WIP: debugging, map implementation

This commit is contained in:
Matthias Koefferlein 2024-08-25 22:33:28 +02:00
parent 7219742b87
commit e7f8290edb
6 changed files with 473 additions and 8 deletions

View File

@ -40,7 +40,7 @@ namespace tl
* with tl::BitSetMap and tl::BitSetMatch.
*
* Allocation is dynamic when a bit is accessed for write. Bits beyond the
* size are treated as false.
* allocated size are treated as "false" or zero.
*/
class TL_PUBLIC BitSet
{
@ -239,4 +239,15 @@ private:
}
namespace std
{
inline void
swap (tl::BitSet &a, tl::BitSet &b)
{
a.swap (b);
}
}
#endif

View File

@ -0,0 +1,31 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2024 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "tlCommon.h"
#include "tlBitSetMap.h"
namespace tl
{
// .. nothing yet ..
}

View File

@ -0,0 +1,338 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2024 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_tlBitSetMap
#define HDR_tlBitSetMap
#include "tlCommon.h"
#include "tlBitSetMask.h"
#include "tlBitSet.h"
#include "tlAssert.h"
#include <algorithm>
namespace tl
{
template <class Value>
struct TL_PUBLIC bit_set_mask_node
{
bit_set_mask_node ()
: mask (), next (0), value ()
{
}
tl::BitSetMask mask;
size_t next;
Value value;
void swap (bit_set_mask_node<Value> &other)
{
std::swap (mask, other.mask);
std::swap (next, other.next);
std::swap (value, other.value);
}
};
template <class Value>
class TL_PUBLIC bit_set_mask_compare
{
public:
bit_set_mask_compare (tl::BitSetMask::index_type bit, tl::BitSetMask::mask_type mask)
: m_bit (bit), m_mask (mask)
{
}
bool operator() (const bit_set_mask_node<Value> &node) const
{
return node.mask [m_bit] < m_mask;
}
private:
tl::BitSetMask::index_type m_bit;
tl::BitSetMask::mask_type m_mask;
};
}
namespace std
{
template <class Value>
inline void
swap (tl::bit_set_mask_node<Value> &a, tl::bit_set_mask_node<Value> &b)
{
a.swap (b);
}
}
namespace tl
{
/**
* @brief A bit set map
*
* This specialized map stores tl::BitSetMask keys and corresponding values.
* tl::BitSet objects can be used to retrieve values. Masks may overlap, hence
* multiple matches are possible. The "lookup" method employs a visitor
* pattern to deliver these multiple matches.
*
* In order to use the map, it first has to be sorted. Insert masks using
* "insert" and do a "sort" before using "lookup".
*/
template <class Value>
class TL_PUBLIC bit_set_map
{
public:
typedef std::vector<bit_set_mask_node<Value> > node_list;
typedef typename node_list::const_iterator const_iterator;
typedef typename node_list::iterator iterator;
/**
* @brief Default constructor: creates an empty bit set
*/
bit_set_map ()
{
// .. nothing yet ..
}
/**
* @brief Copy constructor
*/
bit_set_map (const bit_set_map &other)
{
operator= (other);
}
/**
* @brief Move constructor
*/
bit_set_map (bit_set_map &&other)
{
operator= (std::move (other));
}
/**
* @brief Assignment
*/
bit_set_map &operator= (const bit_set_map &other)
{
if (this != &other) {
m_nodes = other.m_nodes;
m_sorted = other.m_sorted;
}
return *this;
}
/**
* @brief Move assignment
*/
bit_set_map &operator= (bit_set_map &&other)
{
if (this != &other) {
swap (other);
}
return *this;
}
/**
* @brief Swaps the contents of this bit set with the other
*/
void swap (bit_set_map &other)
{
if (this != &other) {
m_nodes.swap (other.m_nodes);
std::swap (m_sorted, other.m_sorted);
}
}
/**
* @brief Clears this map
*/
void clear ()
{
m_nodes.clear ();
m_sorted = true;
}
/**
* @brief Reserves "size" entries
*
* Use this method to specify the intended size of the map.
* This optimizes performance and memory allocation.
*/
void reserve (size_t n)
{
m_nodes.reserve (n);
}
/**
* @brief Inserts an item into the map
*/
void insert (const tl::BitSetMask &mask, const Value &value)
{
m_nodes.push_back (tl::bit_set_mask_node<Value> ());
m_nodes.back ().mask = mask;
m_nodes.back ().next = 0;
m_nodes.back ().value = value;
m_sorted = false;
}
/**
* @brief Sorts the map
*
* "sort" needs to be called before "lookup" can be used.
*/
void sort ()
{
if (! m_sorted) {
sort_range (0, m_nodes.begin (), m_nodes.end ());
m_sorted = true;
}
}
/**
* @brief Gets a value indicating whether the set is empty
*
* "empty" means, no bits have been written yet. "empty" does NOT mean
* all masks are of some specific value.
*/
bool is_empty () const
{
return m_nodes.empty ();
}
/**
* @brief Gets the number of bits for the mask stored
*
* The number of bits is the highest bit written so far.
*/
size_t size () const
{
return m_nodes.size ();
}
/**
* @brief Looks up items by bit set
*
* For each item found, the value is delivered through the
* Inserter provided.
*
* The return value is true, if any value has been found.
*/
template <class Inserter>
bool lookup (const tl::BitSet &bit_set, Inserter inserter)
{
tl_assert (m_sorted);
return partial_lookup (0, m_nodes.begin (), m_nodes.end (), bit_set, inserter);
}
private:
node_list m_nodes;
bool m_sorted;
void sort_range (tl::BitSetMask::index_type bit, iterator from, iterator to)
{
if (from == to) {
return;
}
// special case of identical entries which creates a sequence of
// single entries
bool all_same = true;
for (auto i = from + 1; i != to && all_same; ++i) {
if (*i != *from) {
all_same = false;
}
}
if (all_same) {
// this is also the case for a single element
for (auto i = from + 1; i != to; ++i) {
i->next = 1;
}
return;
}
// we have at least one element. The first one is taken for the previous level node, so we start partitioning
// at the second node
++from;
auto middle_false = std::partition (from, to, tl::bit_set_mask_compare (bit, tl::BitSetMask::False));
auto middle_true = std::partition (middle_false, to, tl::bit_set_mask_compare (bit, tl::BitSetMask::True));
auto middle_never = std::partition (middle_true, to, tl::bit_set_mask_compare (bit, tl::BitSetMask::Never));
from->next = middle_false - from;
if (middle_false != to) {
middle_false->next = middle_true - middle_false;
}
if (middle_true != to) {
middle_true->next = middle_never - middle_true;
}
if (middle_never != to) {
middle_never->next = to - middle_never;
}
sort_range (bit + 1, from, middle_false);
sort_range (bit + 1, middle_false, middle_true);
sort_range (bit + 1, middle_true, middle_never);
sort_range (bit + 1, middle_never, to);
}
template <class Inserter>
bool partial_lookup (tl::BitSetMask::index_type bit, const_iterator from, const_iterator to, const tl::BitSet &bit_set, Inserter inserter)
{
if (from == to) {
return false;
}
bool any = false;
if (from->mask.match (bit_set)) {
*inserter++ = from->value;
any = true;
}
bool b = bit_set [bit];
auto i = ++from;
while (i != to) {
auto m = i->mask [bit];
if (m == tl::BitSetMask::Any || (m == tl::BitSetMask::True && b) || (m == tl::BitSetMask::False && !b)) {
if (partial_lookup (bit + 1, i, i + i->next, bit_set, inserter)) {
any = true;
}
}
if (i->next) {
break;
}
i += i->next;
}
return any;
}
};
}
#endif

View File

@ -265,23 +265,38 @@ BitSetMask::operator[] (index_type index) const
bool
BitSetMask::match (const tl::BitSet &bs) const
{
unsigned nw_bs = nwords (bs.m_size);
unsigned nw = nwords (m_size);
unsigned int nw_bs = nwords (bs.m_size);
unsigned int nw = nwords (m_size);
const tl::BitSet::data_type *d0 = mp_data0, *d1 = mp_data1;
const tl::BitSet::data_type *s = bs.mp_data;
unsigned int i;
for (i = 0; i < nw_bs && i < nw; ++i, ++d0, ++d1, ++s) {
tl::BitSet::data_type d = *s;
for (i = 0; i < nw; ++i, ++d0, ++d1) {
tl::BitSet::data_type d = i < nw_bs ? *s++ : 0;
tl::BitSet::data_type invalid = 0;
if (i >= nw_bs) {
invalid = ~invalid;
} else if (bs.m_size < (i + 1) * (sizeof (tl::BitSet::data_type) * 8)) {
invalid = (1 << ((i + 1) * (sizeof (tl::BitSet::data_type) * 8) - bs.m_size)) - 1;
}
// "never" matches no valid bit ("never" is: d0 and d1 bits are ones)
if (((*d0 & *d1) & ~invalid) != 0) {
return false;
}
// A "true" in place of "false expected" gives "no match"
if ((*d0 & d) != 0) {
if ((*d0 & ~*d1 & d) != 0) {
return false;
}
// A "false" in place of "true expected" gives "no match"
if ((*d1 & ~d) != 0) {
if ((*d1 & ~*d0 & ~d) != 0) {
return false;
}
}
// as "not set" corresponds to "Any", we can stop here and have a match.

View File

@ -45,7 +45,7 @@ namespace tl
* the bit set corresponds to the mask.
*
* Allocation is dynamic when a mask element is accessed for write. Bits beyond the
* size are treated as "Any".
* allocated size are treated as "Any".
*/
class TL_PUBLIC BitSetMask
{
@ -186,4 +186,15 @@ private:
}
namespace std
{
inline void
swap (tl::BitSetMask &a, tl::BitSetMask &b)
{
a.swap (b);
}
}
#endif

View File

@ -49,6 +49,18 @@ static std::string l2s (const tl::BitSetMask &s)
return x;
}
static tl::BitSet bs (const char *s)
{
tl::BitSet res;
for (unsigned int i = 0; *s; ++i, ++s) {
res.set (i);
if (*s == '0') {
res.reset (i);
}
}
return res;
}
TEST(1_Basic)
{
tl::BitSetMask bs;
@ -261,4 +273,51 @@ TEST(4_Assign)
EXPECT_EQ (l2s (bs3), "XXX1XXXXXXXXXXXXXXXXXXXXXXXXXXXX0");
}
TEST(5_Match)
{
tl::BitSetMask bsm;
EXPECT_EQ (bsm.match (bs ("")), true);
EXPECT_EQ (bsm.match (bs ("0")), true);
EXPECT_EQ (bsm.match (bs ("1")), true);
EXPECT_EQ (bsm.match (bs ("10101")), true);
bsm.set (1, tl::BitSetMask::Never);
EXPECT_EQ (l2s (bsm), "X-");
EXPECT_EQ (bsm.match (bs ("")), true);
EXPECT_EQ (bsm.match (bs ("0")), true);
EXPECT_EQ (bsm.match (bs ("1")), true);
EXPECT_EQ (bsm.match (bs ("10101")), false); // fails because "never bit" is used.
bsm.clear ();
bsm.set (1, tl::BitSetMask::True);
bsm.set (2, tl::BitSetMask::False);
EXPECT_EQ (l2s (bsm), "X10");
EXPECT_EQ (bsm.match (bs ("")), false);
EXPECT_EQ (bsm.match (bs ("0")), false);
EXPECT_EQ (bsm.match (bs ("000")), false);
EXPECT_EQ (bsm.match (bs ("001")), false);
EXPECT_EQ (bsm.match (bs ("010")), true);
EXPECT_EQ (bsm.match (bs ("011")), false);
EXPECT_EQ (bsm.match (bs ("1")), false);
EXPECT_EQ (bsm.match (bs ("100")), false);
EXPECT_EQ (bsm.match (bs ("101")), false);
EXPECT_EQ (bsm.match (bs ("110")), true);
EXPECT_EQ (bsm.match (bs ("111")), false);
EXPECT_EQ (bsm.match (bs ("10101")), false);
EXPECT_EQ (bsm.match (bs ("11001")), true);
bsm.clear ();
bsm.set (32, tl::BitSetMask::True);
EXPECT_EQ (bsm.match (bs ("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0")), false);
EXPECT_EQ (bsm.match (bs ("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX1")), true);
EXPECT_EQ (bsm.match (bs ("")), false);
bsm.clear ();
bsm.set (32, tl::BitSetMask::False);
EXPECT_EQ (bsm.match (bs ("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0")), true);
EXPECT_EQ (bsm.match (bs ("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX1")), false);
EXPECT_EQ (bsm.match (bs ("")), true); // because unset bits count as zero
}
}