From 9e79942bb994085dbea42c1388dad58445fe941f Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 25 Jan 2026 10:06:58 +0100 Subject: [PATCH 1/9] Proposing one part of fix --- src/db/db/dbHierarchyBuilder.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/db/db/dbHierarchyBuilder.cc b/src/db/db/dbHierarchyBuilder.cc index b54354858..ca49ebd98 100644 --- a/src/db/db/dbHierarchyBuilder.cc +++ b/src/db/db/dbHierarchyBuilder.cc @@ -390,10 +390,12 @@ HierarchyBuilder::new_inst (const RecursiveShapeIterator *iter, const db::CellIn if (all) { CellMapKey key (inst.object ().cell_index (), iter->is_child_inactive (inst.object ().cell_index ()), std::set ()); + + // NOTE: this will set m_cm_new_entry db::cell_index_type new_cell = make_cell_variant (key, iter->layout ()->cell_name (inst.object ().cell_index ())); // for new cells, create this instance - if (m_cell_stack.back ().first) { + if (m_cell_stack.back ().first || m_cm_new_entry) { db::CellInstArray new_inst (inst, &mp_target->array_repository ()); new_inst.object () = db::CellInst (new_cell); new_inst.transform (always_apply); @@ -432,10 +434,12 @@ HierarchyBuilder::new_inst_member (const RecursiveShapeIterator *iter, const db: } CellMapKey key (inst.object ().cell_index (), iter->is_child_inactive (inst_cell), clip_variant.second); + + // NOTE: this will set m_cm_new_entry db::cell_index_type new_cell = make_cell_variant (key, iter->layout ()->cell_name (inst_cell)); // for a new cell, create this instance - if (m_cell_stack.back ().first) { + if (m_cell_stack.back ().first || m_cm_new_entry) { db::CellInstArray new_inst (db::CellInst (new_cell), always_apply * trans); new_inst.transform_into (m_trans); for (std::vector::const_iterator c = m_cell_stack.back ().second.begin (); c != m_cell_stack.back ().second.end (); ++c) { From 23b314345139f03bf6af4d458cff0cf4a1a30a14 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 25 Jan 2026 13:36:22 +0100 Subject: [PATCH 2/9] Report flat count in deep mode, optimize hierarchy handling 1. In deep mode, the count report column will report hierarchical and flat counts 2. XOR is shortcut evaluated with one empty input. The default implementation does that internally, but at the cost of mapping to the internal working layout which spoils hierarchy. --- src/buddies/src/bd/strmxor.cc | 79 +++++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 26 deletions(-) diff --git a/src/buddies/src/bd/strmxor.cc b/src/buddies/src/bd/strmxor.cc index 7df5be64b..6720d58e8 100644 --- a/src/buddies/src/bd/strmxor.cc +++ b/src/buddies/src/bd/strmxor.cc @@ -29,6 +29,7 @@ #include "dbSaveLayoutOptions.h" #include "dbRegion.h" #include "dbDeepShapeStore.h" +#include "dbCellGraphUtils.h" #include "gsiExpression.h" #include "tlCommandLineParser.h" #include "tlThreads.h" @@ -271,12 +272,13 @@ HealingTileLayoutOutputReceiver::output (const db::Box &box) struct ResultDescriptor { ResultDescriptor () - : shape_count (0), layer_a (-1), layer_b (-1), layer_output (-1), layout (0), top_cell (0) + : shape_count (0), flat_shape_count (0), layer_a (-1), layer_b (-1), layer_output (-1), layout (0), top_cell (0) { // .. nothing yet .. } size_t shape_count; + size_t flat_shape_count; int layer_a; int layer_b; int layer_output; @@ -296,6 +298,20 @@ struct ResultDescriptor } } + size_t flat_count () const + { + if (layout && layer_output >= 0) { + size_t res = 0; + db::CellCounter counter (layout, top_cell); + for (db::Layout::const_iterator c = layout->begin (); c != layout->end (); ++c) { + res += c->shapes (layer_output).size () * counter.weight (c->cell_index ()); + } + return res; + } else { + return flat_shape_count; + } + } + bool is_empty () const { if (layout && layer_output >= 0) { @@ -586,12 +602,7 @@ BD_PUBLIC int strmxor (int argc, char *argv[]) const char *line_format = " %-10s %-12s %s"; - std::string headline; - if (deep) { - headline = tl::sprintf (line_format, tl::to_string (tr ("Layer")), tl::to_string (tr ("Output")), tl::to_string (tr ("Differences (hierarchical shape count)"))); - } else { - headline = tl::sprintf (line_format, tl::to_string (tr ("Layer")), tl::to_string (tr ("Output")), tl::to_string (tr ("Differences (shape count)"))); - } + std::string headline = tl::sprintf (line_format, tl::to_string (tr ("Layer")), tl::to_string (tr ("Output")), tl::to_string (tr ("Differences (shape count)"))); const char *sep = " ----------------------------------------------------------------"; @@ -619,7 +630,11 @@ BD_PUBLIC int strmxor (int argc, char *argv[]) if (r->second.layer_output >= 0 && r->second.layout) { out = r->second.layout->get_properties (r->second.layer_output).to_string (); } - value = tl::to_string (r->second.count ()); + if (deep) { + value = tl::sprintf (tl::to_string (tr ("%lu (hierarchical) %lu (flat)")), r->second.count (), r->second.flat_count ()); + } else { + value = tl::to_string (r->second.count ()); + } } if (! value.empty ()) { tl::info << tl::sprintf (line_format, r->first.second.to_string (), out, value); @@ -846,29 +861,40 @@ class XORTask tl::SelfTimer timer (tl::verbosity () >= 11, "XOR on layer " + m_layer_props.to_string ()); - db::RecursiveShapeIterator ri_a, ri_b; + db::Region xor_res; - if (m_la >= 0) { - ri_a = db::RecursiveShapeIterator (*mp_xor_data->layout_a, mp_xor_data->layout_a->cell (mp_xor_data->cell_a), m_la); - } else { - ri_a = db::RecursiveShapeIterator (*mp_xor_data->layout_a, mp_xor_data->layout_a->cell (mp_xor_data->cell_a), std::vector ()); - } - ri_a.set_for_merged_input (true); + if (m_la < 0) { + + tl_assert (m_lb >= 0); + + db::RecursiveShapeIterator ri_b (*mp_xor_data->layout_b, mp_xor_data->layout_b->cell (mp_xor_data->cell_b), m_lb); + xor_res = db::Region (ri_b, worker->dss (), db::ICplxTrans (mp_xor_data->layout_b->dbu () / m_dbu)); + + } else if (m_lb < 0) { + + db::RecursiveShapeIterator ri_a (*mp_xor_data->layout_a, mp_xor_data->layout_a->cell (mp_xor_data->cell_a), m_la); + xor_res = db::Region (ri_a, worker->dss (), db::ICplxTrans (mp_xor_data->layout_a->dbu () / m_dbu)); - if (m_lb >= 0) { - ri_b = db::RecursiveShapeIterator (*mp_xor_data->layout_b, mp_xor_data->layout_b->cell (mp_xor_data->cell_b), m_lb); } else { - ri_b = db::RecursiveShapeIterator (*mp_xor_data->layout_b, mp_xor_data->layout_b->cell (mp_xor_data->cell_b), std::vector ()); - } - ri_b.set_for_merged_input (true); - db::Region in_a (ri_a, worker->dss (), db::ICplxTrans (mp_xor_data->layout_a->dbu () / m_dbu)); - db::Region in_b (ri_b, worker->dss (), db::ICplxTrans (mp_xor_data->layout_b->dbu () / m_dbu)); + db::RecursiveShapeIterator ri_a (*mp_xor_data->layout_a, mp_xor_data->layout_a->cell (mp_xor_data->cell_a), m_la); + db::RecursiveShapeIterator ri_b (*mp_xor_data->layout_b, mp_xor_data->layout_b->cell (mp_xor_data->cell_b), m_lb); + + db::Region in_a (ri_a, worker->dss (), db::ICplxTrans (mp_xor_data->layout_a->dbu () / m_dbu)); + db::Region in_b (ri_b, worker->dss (), db::ICplxTrans (mp_xor_data->layout_b->dbu () / m_dbu)); + + bool a_empty = in_a.empty (); + bool b_empty = in_b.empty (); + + if (a_empty && ! b_empty) { + xor_res = in_b; + } else if (! a_empty && b_empty) { + xor_res = in_a; + } else if (! a_empty && ! b_empty) { + tl::SelfTimer timer (tl::verbosity () >= 21, "Basic XOR on layer " + m_layer_props.to_string ()); + xor_res = in_a ^ in_b; + } - db::Region xor_res; - { - tl::SelfTimer timer (tl::verbosity () >= 21, "Basic XOR on layer " + m_layer_props.to_string ()); - xor_res = in_a ^ in_b; } int tol_index = 0; @@ -899,6 +925,7 @@ class XORTask xor_res.insert_into (mp_xor_data->output_layout, mp_xor_data->output_cell, result.layer_output); } else { result.shape_count = xor_res.hier_count (); + result.flat_shape_count = xor_res.count (); } } From 9e0572b86337a5432becad4e517c084dbbc29f74 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 25 Jan 2026 19:37:13 +0100 Subject: [PATCH 3/9] Delayed generation of outputs In the multi-threaded case this saves some time. Still for the sample from the issue that is a dominant runtime component and makes a 8-core run slower than the single-core one. --- src/buddies/src/bd/strmxor.cc | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/buddies/src/bd/strmxor.cc b/src/buddies/src/bd/strmxor.cc index 6720d58e8..0330eec48 100644 --- a/src/buddies/src/bd/strmxor.cc +++ b/src/buddies/src/bd/strmxor.cc @@ -35,6 +35,7 @@ #include "tlThreads.h" #include "tlThreadedWorkers.h" #include "tlTimer.h" +#include "tlOptional.h" namespace { @@ -284,6 +285,7 @@ struct ResultDescriptor int layer_output; db::Layout *layout; db::cell_index_type top_cell; + tl::optional results; size_t count () const { @@ -782,8 +784,15 @@ bool run_tiled_xor (const XORData &xor_data) proc.execute ("Running XOR"); } + // no stored results currently + for (std::map, ResultDescriptor>::const_iterator r = xor_data.results->begin (); r != xor_data.results->end (); ++r) { + tl_assert (! r->second.results.has_value ()); + } + // Determines the output status for (std::map, ResultDescriptor>::const_iterator r = xor_data.results->begin (); r != xor_data.results->end () && result; ++r) { + // no stored results currently + tl_assert (! r->second.results.has_value ()); result = r->second.is_empty (); } @@ -922,7 +931,9 @@ class XORTask if (mp_xor_data->output_layout) { result.layer_output = result.layout->insert_layer (lp); - xor_res.insert_into (mp_xor_data->output_layout, mp_xor_data->output_cell, result.layer_output); + if (! xor_res.empty ()) { + result.results = xor_res; + } } else { result.shape_count = xor_res.hier_count (); result.flat_shape_count = xor_res.count (); @@ -991,6 +1002,22 @@ bool run_deep_xor (const XORData &xor_data) job.start (); job.wait (); + // Deliver the outputs + // NOTE: this is done single-threaded and in a delayed fashion as it is not efficient during + // computation and shifting hierarchy of the working layout + + if (xor_data.output_layout) { + + tl::SelfTimer timer (tl::verbosity () >= 11, "Result delivery"); + + for (std::map, ResultDescriptor>::const_iterator r = xor_data.results->begin (); r != xor_data.results->end (); ++r) { + if (r->second.results.has_value ()) { + r->second.results.value ().insert_into (xor_data.output_layout, xor_data.output_cell, r->second.layer_output); + } + } + + } + // Determine the output status bool result = (xor_data.layers_missing == 0); From d326df24247f7321350a80f9713c39b4de1147ad Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 25 Jan 2026 21:58:44 +0100 Subject: [PATCH 4/9] Updating shape count output format and tests --- src/buddies/src/bd/strmxor.cc | 5 ++-- src/buddies/unit_tests/bdStrmxorTests.cc | 30 ++++++++++++------------ 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/buddies/src/bd/strmxor.cc b/src/buddies/src/bd/strmxor.cc index 0330eec48..c19dae98e 100644 --- a/src/buddies/src/bd/strmxor.cc +++ b/src/buddies/src/bd/strmxor.cc @@ -604,7 +604,8 @@ BD_PUBLIC int strmxor (int argc, char *argv[]) const char *line_format = " %-10s %-12s %s"; - std::string headline = tl::sprintf (line_format, tl::to_string (tr ("Layer")), tl::to_string (tr ("Output")), tl::to_string (tr ("Differences (shape count)"))); + std::string headline = tl::sprintf (line_format, tl::to_string (tr ("Layer")), tl::to_string (tr ("Output")), + deep ? tl::to_string (tr ("Differences (hierarchical/flat count)")) : tl::to_string (tr ("Differences (shape count)"))); const char *sep = " ----------------------------------------------------------------"; @@ -633,7 +634,7 @@ BD_PUBLIC int strmxor (int argc, char *argv[]) out = r->second.layout->get_properties (r->second.layer_output).to_string (); } if (deep) { - value = tl::sprintf (tl::to_string (tr ("%lu (hierarchical) %lu (flat)")), r->second.count (), r->second.flat_count ()); + value = tl::sprintf (tl::to_string (tr ("%-6lu / %-6lu")), r->second.count (), r->second.flat_count ()); } else { value = tl::to_string (r->second.count ()); } diff --git a/src/buddies/unit_tests/bdStrmxorTests.cc b/src/buddies/unit_tests/bdStrmxorTests.cc index bb06a9428..caec430a4 100644 --- a/src/buddies/unit_tests/bdStrmxorTests.cc +++ b/src/buddies/unit_tests/bdStrmxorTests.cc @@ -146,11 +146,11 @@ TEST(1A_Deep) "Layer 10/0 is not present in first layout, but in second\n" "Result summary (layers without differences are not shown):\n" "\n" - " Layer Output Differences (hierarchical shape count)\n" + " Layer Output Differences (hierarchical/flat count)\n" " ----------------------------------------------------------------\n" - " 3/0 3/0 3\n" - " 6/0 6/0 314\n" - " 8/1 8/1 1\n" + " 3/0 3/0 3 / 30 \n" + " 6/0 6/0 314 / 314 \n" + " 8/1 8/1 1 / 1 \n" " 10/0 - (no such layer in first layout)\n" "\n" ); @@ -188,11 +188,11 @@ TEST(1A_DeepNoEmptyCells) "Layer 10/0 is not present in first layout, but in second\n" "Result summary (layers without differences are not shown):\n" "\n" - " Layer Output Differences (hierarchical shape count)\n" + " Layer Output Differences (hierarchical/flat count)\n" " ----------------------------------------------------------------\n" - " 3/0 3/0 3\n" - " 6/0 6/0 314\n" - " 8/1 8/1 1\n" + " 3/0 3/0 3 / 30 \n" + " 6/0 6/0 314 / 314 \n" + " 8/1 8/1 1 / 1 \n" " 10/0 - (no such layer in first layout)\n" "\n" ); @@ -248,11 +248,11 @@ TEST(1B_Deep) "Layer 10/0 is not present in first layout, but in second\n" "Result summary (layers without differences are not shown):\n" "\n" - " Layer Output Differences (hierarchical shape count)\n" + " Layer Output Differences (hierarchical/flat count)\n" " ----------------------------------------------------------------\n" - " 3/0 - 3\n" - " 6/0 - 314\n" - " 8/1 - 1\n" + " 3/0 - 3 / 30 \n" + " 6/0 - 314 / 314 \n" + " 8/1 - 1 / 1 \n" " 10/0 - (no such layer in first layout)\n" "\n" ); @@ -830,10 +830,10 @@ TEST(7_OptimizeDeep) EXPECT_EQ (cap.captured_text (), "Result summary (layers without differences are not shown):\n" "\n" - " Layer Output Differences (hierarchical shape count)\n" + " Layer Output Differences (hierarchical/flat count)\n" " ----------------------------------------------------------------\n" - " 2/0 2/0 1\n" - " 3/0 3/0 8\n" + " 2/0 2/0 1 / 12 \n" + " 3/0 3/0 8 / 8 \n" "\n" ); } From 94a425da0d222676b7355d6ca8172a4de86e9bd2 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 26 Jan 2026 23:55:55 +0100 Subject: [PATCH 5/9] WIP --- src/db/db/db.pro | 2 + src/db/db/dbCellInstanceSetHasher.cc | 154 ++++++++++++++++++ src/db/db/dbCellInstanceSetHasher.h | 90 ++++++++++ src/db/db/dbCellMapping.cc | 89 +++++++++- .../dbCellInstanceSetHasherTests.cc | 63 +++++++ src/db/unit_tests/unit_tests.pro | 1 + 6 files changed, 390 insertions(+), 9 deletions(-) create mode 100644 src/db/db/dbCellInstanceSetHasher.cc create mode 100644 src/db/db/dbCellInstanceSetHasher.h create mode 100644 src/db/unit_tests/dbCellInstanceSetHasherTests.cc diff --git a/src/db/db/db.pro b/src/db/db/db.pro index 44edac6b7..188abf0d8 100644 --- a/src/db/db/db.pro +++ b/src/db/db/db.pro @@ -15,6 +15,7 @@ SOURCES = \ dbCellGraphUtils.cc \ dbCellHullGenerator.cc \ dbCellInst.cc \ + dbCellInstanceSetHasher.cc \ dbCellMapping.cc \ dbClipboard.cc \ dbClipboardData.cc \ @@ -253,6 +254,7 @@ HEADERS = \ dbCell.h \ dbCellHullGenerator.h \ dbCellInst.h \ + dbCellInstanceSetHasher.h \ dbCellMapping.h \ dbClipboardData.h \ dbClipboard.h \ diff --git a/src/db/db/dbCellInstanceSetHasher.cc b/src/db/db/dbCellInstanceSetHasher.cc new file mode 100644 index 000000000..a68c7ad22 --- /dev/null +++ b/src/db/db/dbCellInstanceSetHasher.cc @@ -0,0 +1,154 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "dbCellInstanceSetHasher.h" +#include "dbHash.h" + +namespace db +{ + +CellInstanceSetHasher::MatrixHash::MatrixHash (double s) + : db::IMatrix3d (s, 0, 0, 0, s, 0, 0, 0, s) +{ + // .. nothing yet .. +} + +CellInstanceSetHasher::MatrixHash::MatrixHash (const db::ICplxTrans &trans) + : db::IMatrix3d (trans) +{ + // .. nothing yet .. +} + +CellInstanceSetHasher::MatrixHash::MatrixHash (const db::CellInstArray &array) + : db::IMatrix3d (array.complex_trans ()) +{ + db::Vector a, b; + unsigned long na = 0, nb = 0; + + if (array.is_regular_array (a, b, na, nb)) { + + na = std::max ((unsigned long) 1, na); + nb = std::max ((unsigned long) 1, nb); + + // compute the sum of all individual matrices + *this *= double (na * nb); + + db::DVector dab = db::DVector (a) * double ((nb * (na - 1) * na) / 2) + db::DVector (b) * double ((na * (nb - 1) * nb) / 2); + m() [0][2] += dab.x (); + m() [1][2] += dab.y (); + + } else if (array.is_iterated_array ()) { + + db::DVector dab; + double n = 0.0; + + tl_assert (! array.begin ().at_end ()); + db::DVector d0 = db::DVector ((*array.begin ()).disp ()); + for (auto i = array.begin (); ! i.at_end (); ++i) { + n += 1.0; + dab += db::DVector ((*i).disp ()) - d0; + } + + *this *= n; + + m() [0][2] += dab.x (); + m() [1][2] += dab.y (); + + } +} + +static inline size_t d2h (double d) +{ + return d < 0 ? size_t (d - 0.5) : size_t (d + 0.5); +} + +size_t +CellInstanceSetHasher::MatrixHash::hash_value () const +{ + // The "close-to-unity" elements are scaled with this value, so + // after rounding to int for the hash value we are able to + // resolve a certain level of details. This applies to the + // rotation/shear/scale submatrix elements (m11, m12, m21, m22). + const double res = 1024.0; + + size_t h = d2h (m ()[0][0] * res); + h = tl::hcombine (d2h (m ()[0][1] * res), h); + h = tl::hcombine (d2h (m ()[0][2]), h); + h = tl::hcombine (d2h (m ()[1][0] * res), h); + h = tl::hcombine (d2h (m ()[1][1] * res), h); + h = tl::hcombine (d2h (m ()[1][2]), h); + // m31 and m32 are always zero, so we don't count them here + h = tl::hcombine (d2h (m ()[2][2]), h); + return h; +} + +CellInstanceSetHasher::CellInstanceSetHasher (const db::Layout *layout, db::cell_index_type top_cell, const std::set *selection) + : mp_layout (layout), m_top_cell (top_cell), mp_selection (selection) +{ + // .. nothing yet .. +} + +size_t +CellInstanceSetHasher::instance_set_hash (db::cell_index_type for_cell) +{ + return get_hash (for_cell).hash_value (); +} + +CellInstanceSetHasher::MatrixHash +CellInstanceSetHasher::get_hash (cell_index_type for_cell) +{ + auto c = m_cache.find (for_cell); + if (c != m_cache.end ()) { + return c->second; + } else { + MatrixHash hm = get_hash_uncached (for_cell); + m_cache [for_cell] = hm; + return hm; + } +} + +CellInstanceSetHasher::MatrixHash +CellInstanceSetHasher::get_hash_uncached (cell_index_type for_cell) +{ + if (for_cell == m_top_cell) { + + return MatrixHash (); + + } else { + + const db::Cell &fc = mp_layout->cell (for_cell); + + MatrixHash hm (0.0); + for (auto pi = fc.begin_parent_insts (); ! pi.at_end (); ++pi) { + auto pci = pi->parent_cell_index (); + if (! mp_selection || mp_selection->find (pci) != mp_selection->end ()) { + hm += get_hash (pci) * MatrixHash (pi->child_inst ().cell_inst ()); + } + } + + return hm; + + } +} + +} diff --git a/src/db/db/dbCellInstanceSetHasher.h b/src/db/db/dbCellInstanceSetHasher.h new file mode 100644 index 000000000..8cc1cacf6 --- /dev/null +++ b/src/db/db/dbCellInstanceSetHasher.h @@ -0,0 +1,90 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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_dbCellInstanceSetHasher +#define HDR_dbCellInstanceSetHasher + +#include "dbCommon.h" +#include "dbMatrix.h" +#include "dbLayout.h" + +namespace db +{ + +/** + * @brief A hasher for a set of cell instances + * + * The hasher starts with a layout, a top cell and optionally a + * set of cells selected. Only selected cells will be considered + * in the cell tree (the "cone"). + * + * The hasher allows to compute a hash value for a given cell, + * representative for the flat set of instances of that cell in + * the top cell. + */ +class DB_PUBLIC CellInstanceSetHasher +{ +public: + class MatrixHash + : public IMatrix3d + { + public: + MatrixHash (double s = 1.0); + MatrixHash (const db::ICplxTrans &trans); + MatrixHash (const db::CellInstArray &array); + + size_t hash_value () const; + }; + + /** + * @brief Creates a new cell instance set hasher + * + * @param layout The layout the hasher refers to + * @param top_cell The top cell the hasher starts with + * @param selection A set of selected cells or a null pointer if all cells should be considered + * + * The hasher will not take ownership over the layout, nor + * the selected cell set. + */ + CellInstanceSetHasher (const db::Layout *layout, db::cell_index_type top_cell, const std::set *selection = 0); + + /** + * @brief Computes the hash value representative for the flat instance set of the given cell in the top cell and the selection + */ + size_t instance_set_hash (db::cell_index_type for_cell); + +private: + const db::Layout *mp_layout; + db::cell_index_type m_top_cell; + const std::set *mp_selection; + std::map m_cache; + + MatrixHash get_hash (db::cell_index_type for_cell); + MatrixHash get_hash_uncached (db::cell_index_type for_cell); +}; + +} // namespace db + +#endif + diff --git a/src/db/db/dbCellMapping.cc b/src/db/db/dbCellMapping.cc index 7bc3dc712..d79732b4f 100644 --- a/src/db/db/dbCellMapping.cc +++ b/src/db/db/dbCellMapping.cc @@ -122,28 +122,29 @@ struct SortedCellIndexIterator // Some utility class: a compare function for a instance set of two cells in the context // of two layouts and two initial cells. +#if 0 class InstanceSetCompareFunction { public: typedef std::multiset > trans_set_t; - InstanceSetCompareFunction (const db::Layout &layout_a, db::cell_index_type initial_cell_a, const db::Layout &layout_b, db::cell_index_type initial_cell_b) - : m_layout_a (layout_a), m_initial_cell_a (initial_cell_a), - m_layout_b (layout_b), m_initial_cell_b (initial_cell_b), + InstanceSetCompareFunction (const db::Layout &layout_a, db::cell_index_type initial_cell_a, const std::set *selection_cone_a, const db::Layout &layout_b, db::cell_index_type initial_cell_b, const std::set *selection_cone_b) + : m_layout_a (layout_a), m_initial_cell_a (initial_cell_a), m_selection_cone_a (selection_cone_a), + m_layout_b (layout_b), m_initial_cell_b (initial_cell_b), m_selection_cone_b (selection_cone_b), m_cell_a (std::numeric_limits::max ()), m_repr_set (false) { // .. } - bool compare (db::cell_index_type cell_a, const std::set &selection_cone_a, db::cell_index_type cell_b, const std::set &selection_cone_b) + bool compare (db::cell_index_type cell_a, db::cell_index_type cell_b) { if (cell_a != m_cell_a) { m_cell_a = cell_a; m_callers_a.clear (); - m_layout_a.cell (cell_a).collect_caller_cells (m_callers_a, selection_cone_a, -1); + m_layout_a.cell (cell_a).collect_caller_cells (m_callers_a, *m_selection_cone_a, -1); m_callers_a.insert (cell_a); m_trans.clear (); @@ -162,7 +163,7 @@ class InstanceSetCompareFunction } std::set callers_b; - m_layout_b.cell (cell_b).collect_caller_cells (callers_b, selection_cone_b, -1); + m_layout_b.cell (cell_b).collect_caller_cells (callers_b, *m_selection_cone_b, -1); callers_b.insert (cell_b); trans_set_t trans (m_trans); @@ -178,8 +179,10 @@ class InstanceSetCompareFunction private: const db::Layout &m_layout_a; db::cell_index_type m_initial_cell_a; + const std::set *m_selection_cone_a; const db::Layout &m_layout_b; db::cell_index_type m_initial_cell_b; + const std::set *m_selection_cone_b; db::cell_index_type m_cell_a; std::set m_callers_a; trans_set_t m_trans; @@ -255,6 +258,74 @@ class InstanceSetCompareFunction } } }; +#else +class InstanceSetCompareFunction +{ +public: + typedef std::multiset > trans_set_t; + + InstanceSetCompareFunction (const db::Layout &layout_a, db::cell_index_type initial_cell_a, const std::set *selection_cone_a, const db::Layout &layout_b, db::cell_index_type initial_cell_b, const std::set *selection_cone_b) + : m_layout_a (layout_a), m_initial_cell_a (initial_cell_a), m_selection_cone_a (selection_cone_a), + m_layout_b (layout_b), m_initial_cell_b (initial_cell_b), m_selection_cone_b (selection_cone_b) + { + // .. + } + + // @@@ TODO: const method + bool compare (db::cell_index_type cell_a, db::cell_index_type cell_b) + { + return get_trans_set (m_tsa, m_layout_a, m_initial_cell_a, *m_selection_cone_a, cell_a) == get_trans_set (m_tsb, m_layout_b, m_initial_cell_b, *m_selection_cone_b, cell_b); + } + +private: + const db::Layout &m_layout_a; + db::cell_index_type m_initial_cell_a; + const std::set *m_selection_cone_a; // TODO -> ptr @@@ + const db::Layout &m_layout_b; + db::cell_index_type m_initial_cell_b; + const std::set *m_selection_cone_b; // TODO -> ptr @@@ + std::map m_tsa, m_tsb; + + const trans_set_t &get_trans_set (std::map &ts_cache, const db::Layout &layout, db::cell_index_type initial_cell, const std::set &selection, db::cell_index_type for_cell) + { + auto tsi = ts_cache.find (for_cell); + if (tsi == ts_cache.end ()) { + tsi = ts_cache.insert (std::make_pair (for_cell, trans_set_t ())).first; + get_trans_set_uncached (tsi->second, ts_cache, layout, initial_cell, selection, for_cell); + } + return tsi->second; + } + + void get_trans_set_uncached (trans_set_t &ts, std::map &ts_cache, const db::Layout &layout, db::cell_index_type initial_cell, const std::set &selection, db::cell_index_type for_cell) + { + if (for_cell == initial_cell) { + ts.insert (db::ICplxTrans ()); + return; + } + + const db::Cell &c = layout.cell (for_cell); + for (auto p = c.begin_parent_insts (); ! p.at_end (); ++p) { + + const db::CellInstArray &inst = p->child_inst ().cell_inst (); + db::cell_index_type parent_cell = p->parent_cell_index (); + + if (selection.find (inst.object ().cell_index ()) != selection.end ()) { + + const trans_set_t &pts = get_trans_set (ts_cache, layout, initial_cell, selection, parent_cell); + for (auto a = inst.begin (); ! a.at_end (); ++a) { + auto ta = inst.complex_trans (*a); + for (auto pt = pts.begin (); pt != pts.end (); ++pt) { + ts.insert (*pt * ta); + } + + } + + } + + } + } +}; +#endif // ------------------------------------------------------------------------------------- // CellMapping implementation @@ -449,7 +520,7 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty std::map > candidates; // key = index(a), value = indices(b) - InstanceSetCompareFunction cmp (layout_a, cell_index_a, layout_b, cell_index_b); + InstanceSetCompareFunction cmp (layout_a, cell_index_a, &cc_a.selection (), layout_b, cell_index_b, &cc_b.selection ()); std::multimap::const_iterator a = cm_a.begin (), b = cm_b.begin (); while (a != cm_a.end () && b != cm_b.end ()) { @@ -494,7 +565,7 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty if (bg != b_group_of_cell.end ()) { if (groups_taken.find (bg->second) == groups_taken.end ()) { - if (cmp.compare (a->second, cc_a.selection (), bb->second, cc_b.selection ())) { + if (cmp.compare (a->second, bb->second)) { candidates [a->second] = b_group [bg->second]; groups_taken.insert (bg->second); } @@ -502,7 +573,7 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty } else { - if (cmp.compare (a->second, cc_a.selection (), bb->second, cc_b.selection ())) { + if (cmp.compare (a->second, bb->second)) { candidates [a->second].push_back (bb->second); b_group_of_cell.insert (std::make_pair (bb->second, g)); b_group.insert (std::make_pair (g, std::vector ())).first->second.push_back (bb->second); diff --git a/src/db/unit_tests/dbCellInstanceSetHasherTests.cc b/src/db/unit_tests/dbCellInstanceSetHasherTests.cc new file mode 100644 index 000000000..2e0799406 --- /dev/null +++ b/src/db/unit_tests/dbCellInstanceSetHasherTests.cc @@ -0,0 +1,63 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "dbCellInstanceSetHasher.h" +#include "tlUnitTest.h" + +TEST(1) +{ + EXPECT_EQ (db::CellInstanceSetHasher::MatrixHash ().to_string (), "(1,0,0) (0,1,0) (0,0,1)"); + EXPECT_EQ (db::CellInstanceSetHasher::MatrixHash (0).to_string (), "(0,0,0) (0,0,0) (0,0,0)"); + EXPECT_EQ (db::CellInstanceSetHasher::MatrixHash (db::ICplxTrans (2.0, 90.0, false, db::Vector (1, 2))).to_string (), "(0,-2,1) (2,0,2) (0,0,1)"); + + db::ICplxTrans t0 (2.0, 90.0, false, db::Vector (1, 2)); + + db::CellInstArray array (db::CellInst (0), t0, db::Vector (0, 100), db::Vector (100, 0), 2, 3); + EXPECT_EQ (db::CellInstanceSetHasher::MatrixHash (array).to_string (), "(0,-12,606) (12,0,312) (0,0,6)"); + + // emulate the regular array with an iterated array + std::vector dd; + for (unsigned int i = 0; i < 2; ++i) { + for (unsigned int j = 0; j < 3; ++j) { + db::Vector d = db::Vector (0, 100 * i) + db::Vector (100 * j, 0); + dd.push_back (d); + } + } + db::CellInstArray iter_array (db::CellInst (0), t0, dd.begin (), dd.end ()); + EXPECT_EQ (db::CellInstanceSetHasher::MatrixHash (iter_array).to_string (), db::CellInstanceSetHasher::MatrixHash (array).to_string ()); + + // equivalence of sum of matrices and computed matrix for array + db::CellInstanceSetHasher::MatrixHash hm (0.0); + for (unsigned int i = 0; i < 2; ++i) { + for (unsigned int j = 0; j < 3; ++j) { + db::Vector d = db::Vector (0, 100 * i) + db::Vector (100 * j, 0); + hm += db::CellInstanceSetHasher::MatrixHash (db::ICplxTrans (d) * t0); + } + } + EXPECT_EQ (db::CellInstanceSetHasher::MatrixHash (hm).to_string (), db::CellInstanceSetHasher::MatrixHash (array).to_string ()); +} + +TEST(2) +{ + // @@@ +} diff --git a/src/db/unit_tests/unit_tests.pro b/src/db/unit_tests/unit_tests.pro index ace029ba3..a51884427 100644 --- a/src/db/unit_tests/unit_tests.pro +++ b/src/db/unit_tests/unit_tests.pro @@ -7,6 +7,7 @@ TARGET = db_tests include($$PWD/../../lib_ut.pri) SOURCES = \ + dbCellInstanceSetHasherTests.cc \ dbCompoundOperationTests.cc \ dbEdgeNeighborhoodTests.cc \ dbFillToolTests.cc \ From 6661f5b5dd0c862c6e31a62978236fc0f3d56e4d Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 27 Jan 2026 20:54:24 +0100 Subject: [PATCH 6/9] WIP --- .../dbCellInstanceSetHasherTests.cc | 84 ++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/src/db/unit_tests/dbCellInstanceSetHasherTests.cc b/src/db/unit_tests/dbCellInstanceSetHasherTests.cc index 2e0799406..7bbaff206 100644 --- a/src/db/unit_tests/dbCellInstanceSetHasherTests.cc +++ b/src/db/unit_tests/dbCellInstanceSetHasherTests.cc @@ -59,5 +59,87 @@ TEST(1) TEST(2) { - // @@@ + db::Layout ly; + + db::cell_index_type top = ly.add_cell ("TOP"); + db::cell_index_type c1 = ly.add_cell ("C1"); + db::cell_index_type c2 = ly.add_cell ("C2"); + db::cell_index_type c3 = ly.add_cell ("C3"); + db::cell_index_type c4a = ly.add_cell ("C4A"); + db::cell_index_type c5a = ly.add_cell ("C5A"); + db::cell_index_type c4b = ly.add_cell ("C4B"); + db::cell_index_type c5b = ly.add_cell ("C5B"); + + ly.cell (top).insert (db::CellInstArray (db::CellInst (c1), db::Trans (1, true, db::Vector (0, 0)))); + ly.cell (top).insert (db::CellInstArray (db::CellInst (c1), db::Trans (0, false, db::Vector (0, 10000)))); + + ly.cell (c1).insert (db::CellInstArray (db::CellInst (c2), db::Trans (1, true, db::Vector (100, 200)), db::Vector (0, 1000), db::Vector (1000, 0), 2l, 3l)); + + // C4 and C5 are single instances in C2, C5 with mag 2 + ly.cell (c2).insert (db::CellInstArray (db::CellInst (c4a), db::ICplxTrans (1.0, 0.0, false, db::Vector (10, 20)))); + ly.cell (c2).insert (db::CellInstArray (db::CellInst (c5a), db::ICplxTrans (2.0, 0.0, false, db::Vector (10, 20)))); + + // C3 has same instances as C2, but flat + ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (1, true, db::Vector (100, 10200)))); + ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (1, true, db::Vector (100, 11200)))); + ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (1, true, db::Vector (1100, 10200)))); + ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (1, true, db::Vector (1100, 11200)))); + ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (1, true, db::Vector (2100, 10200)))); + ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (1, true, db::Vector (2100, 11200)))); + ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (0, false, db::Vector (200, 100)))); + ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (0, false, db::Vector (200, 1100)))); + ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (0, false, db::Vector (200, 2100)))); + ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (0, false, db::Vector (1200, 100)))); + ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (0, false, db::Vector (1200, 1100)))); + ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (0, false, db::Vector (1200, 2100)))); + + // C4 and C5 are single instances in C3, C5 with a different complex angle (45 degree) + ly.cell (c3).insert (db::CellInstArray (db::CellInst (c4b), db::ICplxTrans (1.0, 0.0, false, db::Vector (10, 20)))); + ly.cell (c3).insert (db::CellInstArray (db::CellInst (c5b), db::ICplxTrans (1.0, 45.0, false, db::Vector (10, 20)))); + + db::CellInstanceSetHasher hasher1 (&ly, top, 0); + + EXPECT_EQ (tl::sprintf ("%08lx", hasher1.instance_set_hash (top)), "00004450"); + EXPECT_EQ (tl::sprintf ("%08lx", hasher1.instance_set_hash (c1)), "00023711"); + EXPECT_EQ (tl::sprintf ("%08lx", hasher1.instance_set_hash (c2)), "001260aa"); + EXPECT_EQ (hasher1.instance_set_hash (c3), hasher1.instance_set_hash (c2)); + EXPECT_EQ (tl::sprintf ("%08lx", hasher1.instance_set_hash (c4a)), "001270ba"); + EXPECT_EQ (hasher1.instance_set_hash (c4a), hasher1.instance_set_hash (c4b)); + EXPECT_EQ (tl::sprintf ("%08lx", hasher1.instance_set_hash (c5a)), "0010da3a"); // != hash of C4A because of mag 2 + EXPECT_EQ (tl::sprintf ("%08lx", hasher1.instance_set_hash (c5b)), "0011d5c4"); // != hash of C5A because of 45 degree angle + + std::set set1; + set1.insert (top); + set1.insert (c1); + set1.insert (c2); + set1.insert (c3); + set1.insert (c4a); + set1.insert (c5a); + set1.insert (c4b); + set1.insert (c5b); + db::CellInstanceSetHasher hasher2 (&ly, top, &set1); + + EXPECT_EQ (hasher1.instance_set_hash (top), hasher2.instance_set_hash (top)); + EXPECT_EQ (hasher1.instance_set_hash (c1), hasher2.instance_set_hash (c1)); + EXPECT_EQ (hasher1.instance_set_hash (c2), hasher2.instance_set_hash (c2)); + EXPECT_EQ (hasher1.instance_set_hash (c3), hasher2.instance_set_hash (c3)); + EXPECT_EQ (hasher1.instance_set_hash (c4a), hasher2.instance_set_hash (c4a)); + EXPECT_EQ (hasher1.instance_set_hash (c4b), hasher2.instance_set_hash (c4b)); + EXPECT_EQ (hasher1.instance_set_hash (c5a), hasher2.instance_set_hash (c5a)); + EXPECT_EQ (hasher1.instance_set_hash (c5b), hasher2.instance_set_hash (c5b)); + + std::set set2 = set1; + // Remove C1 from selected set + set2.erase (c1); + db::CellInstanceSetHasher hasher3 (&ly, top, &set2); + + EXPECT_EQ (tl::sprintf ("%08lx", hasher3.instance_set_hash (top)), "00004450"); + // NOTE: C1 hash is not valid as this cell is not selected + EXPECT_EQ (tl::sprintf ("%08lx", hasher3.instance_set_hash (c2)), "00000000"); // no path to TOP + EXPECT_EQ (tl::sprintf ("%08lx", hasher3.instance_set_hash (c3)), "001260aa"); + EXPECT_EQ (tl::sprintf ("%08lx", hasher3.instance_set_hash (c4a)), "00000000"); // no path to TOP + EXPECT_EQ (tl::sprintf ("%08lx", hasher3.instance_set_hash (c4b)), "001270ba"); + EXPECT_EQ (tl::sprintf ("%08lx", hasher3.instance_set_hash (c5a)), "00000000"); // no path to TOP + EXPECT_EQ (tl::sprintf ("%08lx", hasher3.instance_set_hash (c5b)), "0011d5c4"); // != hash of C5A because of 45 degree angle } + From c12942c5bc0c0050e187b78dc4bb9762af9cd7b2 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 31 Jan 2026 21:53:28 +0100 Subject: [PATCH 7/9] [consider merging] Fixed a MT problem that can be fixed by avoiding recursive calls of Layout::update --- src/db/db/dbBoxTree.h | 30 ++++++++------ src/db/db/dbCell.cc | 79 +++++++++++++++++++++++++++++++++--- src/db/db/dbCell.h | 54 ++++++++++++++++--------- src/db/db/dbInstances.cc | 86 ++++++++++++++++++++++++++++++++++++++-- src/db/db/dbLayout.cc | 21 +++++----- 5 files changed, 221 insertions(+), 49 deletions(-) diff --git a/src/db/db/dbBoxTree.h b/src/db/db/dbBoxTree.h index 5ceb905e6..ad1a6be2e 100644 --- a/src/db/db/dbBoxTree.h +++ b/src/db/db/dbBoxTree.h @@ -981,9 +981,10 @@ class box_tree * Only after sorting the query iterators are available. * Sorting complexity is approx O(N*log(N)). */ - void sort (const BoxConv &conv) + template + void sort (const BC &conv) { - typename BoxConv::complexity complexity_tag; + typename BC::complexity complexity_tag; sort (conv, complexity_tag); } @@ -1192,7 +1193,8 @@ class box_tree box_tree_node *mp_root; /// Sort implementation for simple bboxes - no caching - void sort (const BoxConv &conv, const db::simple_bbox_tag &/*complexity*/) + template + void sort (const BC &conv, const db::simple_bbox_tag &/*complexity*/) { m_elements.clear (); m_elements.reserve (m_objects.size ()); @@ -1204,7 +1206,7 @@ class box_tree if (! m_objects.empty ()) { - box_tree_picker_type picker (conv); + box_tree_picker picker (conv); box_type bbox; for (typename obj_vector_type::const_iterator o = m_objects.begin (); o != m_objects.end (); ++o) { @@ -1221,7 +1223,8 @@ class box_tree } /// Sort implementation for complex bboxes - with caching - void sort (const box_conv_type &conv, const db::complex_bbox_tag &/*complexity*/) + template + void sort (const BC &conv, const db::complex_bbox_tag &/*complexity*/) { m_elements.clear (); m_elements.reserve (m_objects.size ()); @@ -1233,7 +1236,7 @@ class box_tree if (! m_objects.empty ()) { - box_tree_cached_picker picker (conv, m_objects.begin (), m_objects.end ()); + box_tree_cached_picker picker (conv, m_objects.begin (), m_objects.end ()); for (typename obj_vector_type::const_iterator o = m_objects.begin (); o != m_objects.end (); ++o) { m_elements.push_back (o.index ()); @@ -1997,9 +2000,10 @@ class unstable_box_tree * Only after sorting the query iterators are available. * Sorting complexity is approx O(N*log(N)). */ - void sort (const BoxConv &conv) + template + void sort (const BC &conv) { - typename BoxConv::complexity complexity_tag; + typename BC::complexity complexity_tag; sort (conv, complexity_tag); } @@ -2159,13 +2163,14 @@ class unstable_box_tree box_tree_node *mp_root; /// Sort implementation for simple bboxes - no caching - void sort (const BoxConv &conv, const db::simple_bbox_tag &/*complexity*/) + template + void sort (const BC &conv, const db::simple_bbox_tag &/*complexity*/) { if (m_objects.empty ()) { return; } - box_tree_picker_type picker (conv); + box_tree_picker picker (conv); if (mp_root) { delete mp_root; @@ -2184,13 +2189,14 @@ class unstable_box_tree } /// Sort implementation for complex bboxes - with caching - void sort (const box_conv_type &conv, const db::complex_bbox_tag &/*complexity*/) + template + void sort (const BC &conv, const db::complex_bbox_tag &/*complexity*/) { if (m_objects.empty ()) { return; } - box_tree_cached_picker picker (conv, m_objects.begin (), m_objects.end ()); + box_tree_cached_picker picker (conv, m_objects.begin (), m_objects.end ()); if (mp_root) { delete mp_root; diff --git a/src/db/db/dbCell.cc b/src/db/db/dbCell.cc index cb56e7354..7f54211d1 100644 --- a/src/db/db/dbCell.cc +++ b/src/db/db/dbCell.cc @@ -274,11 +274,67 @@ Cell::is_shape_bbox_dirty () const return false; } +namespace +{ + +/** + * @brief An alternative box converter for CellInst which does not call layout.update + * + * Using this converter in cell.update_bbox() prevents recursive calls to layout.update. + */ +class InternalCellInstBoxConverter +{ +public: + typedef db::Cell::box_type box_type; + typedef db::simple_bbox_tag complexity; + + InternalCellInstBoxConverter (const db::Layout *layout) + : mp_layout (layout) + { + // .. nothing yet .. + } + + box_type operator() (const db::CellInst &cell_inst) const + { + return mp_layout->cell (cell_inst.cell_index ()).bbox_with_empty_no_update (); + } + +private: + const db::Layout *mp_layout; +}; + +/** + * @brief An alternative box converter for CellInst and layer which does not call layout.update + * + * Using this converter in cell.update_bbox() prevents recursive calls to layout.update. + */ +class InternalPerLayerCellInstBoxConverter +{ +public: + typedef db::Cell::box_type box_type; + typedef db::simple_bbox_tag complexity; + + InternalPerLayerCellInstBoxConverter (const db::Layout *layout, unsigned int layer) + : mp_layout (layout), m_layer (layer) + { + // .. nothing yet .. + } + + box_type operator() (const db::CellInst &cell_inst) const + { + return mp_layout->cell (cell_inst.cell_index ()).bbox_no_update (m_layer); + } + +private: + const db::Layout *mp_layout; + unsigned int m_layer; +}; + +} + bool Cell::update_bbox (unsigned int layers) { - unsigned int l; - // determine the bounding box box_type org_bbox = m_bbox; m_bbox = box_type (); @@ -308,10 +364,10 @@ Cell::update_bbox (unsigned int layers) ++o; } - for (l = 0; l < layers; ++l) { + for (unsigned int l = 0; l < layers; ++l) { // the per-layer bounding boxes - db::box_convert bc (*mp_layout, l); + InternalPerLayerCellInstBoxConverter bc (mp_layout, l); box_type lbox = o1_inst->bbox_from_raw_bbox (raw_box, bc); if (! lbox.empty ()) { @@ -326,7 +382,7 @@ Cell::update_bbox (unsigned int layers) } - db::box_convert bc_we (*mp_layout); + InternalCellInstBoxConverter bc_we (mp_layout); m_bbox_with_empty += o1_inst->bbox_from_raw_bbox (raw_box, bc_we); } @@ -486,6 +542,17 @@ Cell::bbox (unsigned int l) const } } +const Cell::box_type & +Cell::bbox_no_update (unsigned int l) const +{ + box_map::const_iterator b = m_bboxes.find (l); + if (b != m_bboxes.end ()) { + return b->second; + } else { + return ms_empty_box; + } +} + Cell::const_iterator Cell::begin () const { @@ -725,7 +792,7 @@ Cell::count_hier_levels () const { unsigned int l = 0; - for (const_iterator c = begin (); !c.at_end (); ++c) { + for (const_iterator c = m_instances.begin (); !c.at_end (); ++c) { l = std::max (l, (unsigned int) mp_layout->cell (c->cell_index ()).m_hier_levels + 1); } diff --git a/src/db/db/dbCell.h b/src/db/db/dbCell.h index 7ba54fc71..44660962d 100644 --- a/src/db/db/dbCell.h +++ b/src/db/db/dbCell.h @@ -522,21 +522,6 @@ class DB_PUBLIC Cell */ bool is_shape_bbox_dirty () const; - /** - * @brief Updates the bbox - * - * This will update the bbox from the shapes and instances. - * This requires the bboxes of the child cells to be computed - * before. Practically this will be done by computing the - * bboxes bottom-up in the hierarchy. - * In addition, the number of hierarchy levels below is also - * updated. - * - * @param layers The max. number of layers in the child cells - * @return true, if the bounding box has changed. - */ - bool update_bbox (unsigned int layers); - /** * @brief Sorts the shapes lists * @@ -1110,6 +1095,23 @@ class DB_PUBLIC Cell */ void move_shapes (db::Cell &source_cell, const db::LayerMapping &layer_mapping); + /** + * @brief Gets the current bounding box (with empty) without calling Layout::update + * + * This method is intended for internal purposes only. + */ + const box_type &bbox_with_empty_no_update () const + { + return m_bbox_with_empty; + } + + /** + * @brief Gets the current per-layer bounding box without calling Layout::update + * + * This method is intended for internal purposes only. + */ + const box_type &bbox_no_update (unsigned int l) const; + protected: /** * @brief Standard constructor: create an empty cell object @@ -1135,6 +1137,7 @@ class DB_PUBLIC Cell virtual Cell *clone (db::Layout &layout) const; private: + friend class db::Layout; cell_index_type m_cell_index; mutable db::Layout *mp_layout; shapes_map m_shapes_map; @@ -1170,9 +1173,9 @@ class DB_PUBLIC Cell } /** - * @brief Return a reference to the instances object + * @brief Return a reference to the instances object */ - instances_type &instances () + const instances_type &instances () const { return m_instances; } @@ -1180,7 +1183,7 @@ class DB_PUBLIC Cell /** * @brief Return a reference to the instances object */ - const instances_type &instances () const + instances_type &instances () { return m_instances; } @@ -1219,6 +1222,21 @@ class DB_PUBLIC Cell * @param force Force sorting, even if not strictly needed */ void sort_inst_tree (bool force); + + /** + * @brief Updates the bbox + * + * This will update the bbox from the shapes and instances. + * This requires the bboxes of the child cells to be computed + * before. Practically this will be done by computing the + * bboxes bottom-up in the hierarchy. + * In addition, the number of hierarchy levels below is also + * updated. + * + * @param layers The max. number of layers in the child cells + * @return true, if the bounding box has changed. + */ + bool update_bbox (unsigned int layers); }; /** diff --git a/src/db/db/dbInstances.cc b/src/db/db/dbInstances.cc index 0e43a2293..d4245a51a 100644 --- a/src/db/db/dbInstances.cc +++ b/src/db/db/dbInstances.cc @@ -1524,6 +1524,84 @@ Instances::sort_child_insts (bool force) std::sort (m_insts_by_cell_index.begin (), m_insts_by_cell_index.end (), cell_inst_compare_f ()); } +namespace { + +/** + * @brief An alternative box converter for CellInst which does not call layout.update + * + * Using this converter in cell.update_bbox() prevents recursive calls to layout.update. + */ +class InternalCellInstBoxConverter +{ +public: + typedef db::Cell::box_type box_type; + typedef db::simple_bbox_tag complexity; + + InternalCellInstBoxConverter (const db::Layout *layout) + : mp_layout (layout) + { + // .. nothing yet .. + } + + db::Cell::box_type operator() (const db::Cell::cell_inst_type &cell_inst) const + { + return mp_layout->cell (cell_inst.cell_index ()).bbox_with_empty_no_update (); + } + +private: + const db::Layout *mp_layout; +}; + +/** + * @brief An alternative box converter for CellInstArray with properties which does not call layout.update + * + * Using this converter in cell.update_bbox() prevents recursive calls to layout.update. + */ +struct InternalCellInstArrayWithPropertiesBoxConverter +{ + typedef db::Cell::cell_inst_array_type cell_inst_array; + typedef db::Cell::box_type box_type; + typedef db::complex_bbox_tag complexity; + + InternalCellInstArrayWithPropertiesBoxConverter (const db::Layout *layout) + : m_bc (layout) + { } + + box_type operator() (const db::object_with_properties &array) const + { + return array.bbox (m_bc); + } + +private: + InternalCellInstBoxConverter m_bc; +}; + +/** + * @brief An alternative box converter for CellInstArray which does not call layout.update + * + * Using this converter in cell.update_bbox() prevents recursive calls to layout.update. + */ +struct InternalCellInstArrayBoxConverter +{ + typedef db::Cell::cell_inst_array_type cell_inst_array; + typedef db::Cell::box_type box_type; + typedef db::complex_bbox_tag complexity; + + InternalCellInstArrayBoxConverter (const db::Layout *layout) + : m_bc (layout) + { } + + box_type operator() (const cell_inst_array &array) const + { + return array.bbox (m_bc); + } + +private: + InternalCellInstBoxConverter m_bc; +}; + +} + void Instances::sort_inst_tree (const Layout *g, bool force) { @@ -1534,18 +1612,18 @@ Instances::sort_inst_tree (const Layout *g, bool force) if (m_generic.any) { if (is_editable ()) { - m_generic.stable_tree->sort (cell_inst_array_box_converter (*g)); + m_generic.stable_tree->sort (InternalCellInstArrayBoxConverter (g)); } else { - m_generic.unstable_tree->sort (cell_inst_array_box_converter (*g)); + m_generic.unstable_tree->sort (InternalCellInstArrayBoxConverter (g)); // since we use unstable instance trees in non-editable mode, we need to resort the child instances in this case sort_child_insts (true); } } if (m_generic_wp.any) { if (is_editable ()) { - m_generic_wp.stable_tree->sort (cell_inst_wp_array_box_converter (*g)); + m_generic_wp.stable_tree->sort (InternalCellInstArrayWithPropertiesBoxConverter (g)); } else { - m_generic_wp.unstable_tree->sort (cell_inst_wp_array_box_converter (*g)); + m_generic_wp.unstable_tree->sort (InternalCellInstArrayWithPropertiesBoxConverter (g)); // since we use unstable instance trees in non-editable mode, we need to resort the child instances in this case sort_child_insts (true); } diff --git a/src/db/db/dbLayout.cc b/src/db/db/dbLayout.cc index fbf1abf14..c14df882a 100644 --- a/src/db/db/dbLayout.cc +++ b/src/db/db/dbLayout.cc @@ -1532,6 +1532,8 @@ Layout::rename_cell (cell_index_type id, const char *name) bool Layout::topological_sort () { + // NOTE: using cell.instances methods instead of the corresponding cell methods avoids a recursive update call + m_top_cells = 0; m_top_down_list.clear (); @@ -1559,7 +1561,7 @@ Layout::topological_sort () // child cells. for (const_iterator c = begin (); c != end (); ++c) { - if (c->parent_cells () == num_parents [c->cell_index ()]) { + if (c->instances ().parent_cells () == num_parents [c->cell_index ()]) { m_top_down_list.push_back (c->cell_index ()); num_parents [c->cell_index ()] = std::numeric_limits::max (); } @@ -1568,7 +1570,7 @@ Layout::topological_sort () // For all these a cells, increment the reported parent instance // count in all the child cells. for (cell_index_vector::const_iterator ii = m_top_down_list.begin () + n_top_down_cells; ii != m_top_down_list.end (); ++ii) { - for (cell_type::child_cell_iterator cc = cell (*ii).begin_child_cells (); ! cc.at_end (); ++cc) { + for (cell_type::child_cell_iterator cc = cell (*ii).instances ().begin_child_cells (); ! cc.at_end (); ++cc) { tl_assert (num_parents [*cc] != std::numeric_limits::max ()); num_parents [*cc] += 1; } @@ -1583,7 +1585,7 @@ Layout::topological_sort () } // Determine the number of top cells - for (top_down_iterator e = m_top_down_list.begin (); e != m_top_down_list.end () && cell (*e).is_top (); ++e) { + for (top_down_iterator e = m_top_down_list.begin (); e != m_top_down_list.end () && cell (*e).instances ().is_top (); ++e) { ++m_top_cells; } @@ -1815,6 +1817,8 @@ Layout::force_update_no_lock () const void Layout::update () const { + tl::MutexLocker locker (&lock ()); + // NOTE: the assumption is that either one thread is writing or // multiple threads are reading. Hence, we do not need to lock hier_dirty() or bboxes_dirty(). // We still do double checking as another thread might do the update as well. @@ -1822,8 +1826,6 @@ Layout::update () const return; } - tl::MutexLocker locker (&lock ()); - if (! under_construction ()) { force_update_no_lock (); } @@ -1886,13 +1888,14 @@ Layout::do_update () unsigned int layers = 0; pr->set (0); pr->set_desc (tl::to_string (tr ("Updating bounding boxes"))); - for (bottom_up_iterator c = begin_bottom_up (); c != end_bottom_up (); ++c) { + for (bottom_up_iterator c = m_top_down_list.rbegin (); c != m_top_down_list.rend (); ++c) { ++*pr; cell_type &cp (cell (*c)); if (cp.is_shape_bbox_dirty () || dirty_parents.find (*c) != dirty_parents.end ()) { if (cp.update_bbox (layers)) { // the bounding box has changed - need to insert parents into "dirty parents" list - for (cell_type::parent_cell_iterator p = cp.begin_parent_cells (); p != cp.end_parent_cells (); ++p) { + // NOTE: using "instances" instead of the cell directly avoids a recursive update call + for (cell_type::parent_cell_iterator p = cp.instances ().begin_parent_cells (); p != cp.instances ().end_parent_cells (); ++p) { dirty_parents.insert (*p); } } @@ -1907,7 +1910,7 @@ Layout::do_update () tl::SelfTimer timer (tl::verbosity () > layout_base_verbosity + 10, "Sorting shapes"); pr->set (0); pr->set_desc (tl::to_string (tr ("Sorting shapes"))); - for (bottom_up_iterator c = begin_bottom_up (); c != end_bottom_up (); ++c) { + for (bottom_up_iterator c = m_top_down_list.rbegin (); c != m_top_down_list.rend (); ++c) { ++*pr; cell_type &cp (cell (*c)); cp.sort_shapes (); @@ -1922,7 +1925,7 @@ Layout::do_update () size_t layers = 0; pr->set (0); pr->set_desc (tl::to_string (tr ("Sorting instances"))); - for (bottom_up_iterator c = begin_bottom_up (); c != end_bottom_up (); ++c) { + for (bottom_up_iterator c = m_top_down_list.rbegin (); c != m_top_down_list.rend (); ++c) { ++*pr; cell_type &cp (cell (*c)); bool force_sort_inst_tree = dirty_parents.find (*c) != dirty_parents.end (); From 0067d46541161ea964114a4a86bc27796ef95307 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 1 Feb 2026 20:27:49 +0100 Subject: [PATCH 8/9] Providing a (maybe faster, sometimes) backup implementation for cell mapping - to be used later maybe --- src/db/db/dbCellMapping.cc | 172 ++++++++++++++++++++++++++++--------- src/db/db/dbLayerMapping.h | 2 +- 2 files changed, 132 insertions(+), 42 deletions(-) diff --git a/src/db/db/dbCellMapping.cc b/src/db/db/dbCellMapping.cc index d79732b4f..26d5770e5 100644 --- a/src/db/db/dbCellMapping.cc +++ b/src/db/db/dbCellMapping.cc @@ -25,6 +25,8 @@ #include "dbCellGraphUtils.h" #include "dbCellMapping.h" #include "dbLayoutUtils.h" +#include "dbCellInstanceSetHasher.h" +#include "dbHash.h" #include "tlLog.h" #include "tlTimer.h" @@ -122,11 +124,12 @@ struct SortedCellIndexIterator // Some utility class: a compare function for a instance set of two cells in the context // of two layouts and two initial cells. -#if 0 -class InstanceSetCompareFunction +#if 1 + +class InstanceSetCompareFunction { public: - typedef std::multiset > trans_set_t; + typedef std::unordered_multiset trans_set_t; InstanceSetCompareFunction (const db::Layout &layout_a, db::cell_index_type initial_cell_a, const std::set *selection_cone_a, const db::Layout &layout_b, db::cell_index_type initial_cell_b, const std::set *selection_cone_b) : m_layout_a (layout_a), m_initial_cell_a (initial_cell_a), m_selection_cone_a (selection_cone_a), @@ -176,6 +179,11 @@ class InstanceSetCompareFunction return trans.empty (); } + void make_endpoint (db::cell_index_type /*cell_a*/, db::cell_index_type /*cell_b*/) + { + // not supported by this version + } + private: const db::Layout &m_layout_a; db::cell_index_type m_initial_cell_a; @@ -258,73 +266,145 @@ class InstanceSetCompareFunction } } }; + #else + +/* + * An alternative implementation of the InstanceSetCompareFunction + * + * This allows setting "endpoints" which are basically known cell identities + * and allow shortcutting the instance set comparison. They act as additional + * top cells. + * + * This implementation has a potential for higher performance, but + * in general it requires somewhat more memory and is not fully optimized yet. + */ + +namespace { + +struct InstanceSetCompareFunctionHash +{ + std::size_t operator () (const std::pair &p) const + { + return tl::hcombine (p.first, tl::hfunc (p.second)); + } +}; + +} + class InstanceSetCompareFunction { public: - typedef std::multiset > trans_set_t; + typedef std::unordered_multiset, InstanceSetCompareFunctionHash> trans_set_t; InstanceSetCompareFunction (const db::Layout &layout_a, db::cell_index_type initial_cell_a, const std::set *selection_cone_a, const db::Layout &layout_b, db::cell_index_type initial_cell_b, const std::set *selection_cone_b) - : m_layout_a (layout_a), m_initial_cell_a (initial_cell_a), m_selection_cone_a (selection_cone_a), - m_layout_b (layout_b), m_initial_cell_b (initial_cell_b), m_selection_cone_b (selection_cone_b) + : m_layout_a (layout_a), m_selection_cone_a (selection_cone_a), + m_layout_b (layout_b), m_selection_cone_b (selection_cone_b), + m_cell_a (std::numeric_limits::max ()), + m_ep_index (0) { - // .. + m_endpoints_a.insert (std::make_pair (initial_cell_a, m_ep_index)); + m_endpoints_b.insert (std::make_pair (initial_cell_b, m_ep_index)); + ++m_ep_index; } - // @@@ TODO: const method bool compare (db::cell_index_type cell_a, db::cell_index_type cell_b) { - return get_trans_set (m_tsa, m_layout_a, m_initial_cell_a, *m_selection_cone_a, cell_a) == get_trans_set (m_tsb, m_layout_b, m_initial_cell_b, *m_selection_cone_b, cell_b); + if (cell_a != m_cell_a) { + m_cell_a = cell_a; + m_trans.clear (); + collect_or_compare_trans_set (true, m_trans, m_endpoints_a, m_layout_a, *m_selection_cone_a, m_cell_a, db::ICplxTrans ()); + } + + trans_set_t trans (m_trans); + + double mag = m_layout_b.dbu () / m_layout_a.dbu (); + if (! collect_or_compare_trans_set (false, trans, m_endpoints_b, m_layout_b, *m_selection_cone_b, cell_b, db::ICplxTrans (mag))) { + return false; + } else { + return trans.empty (); + } + } + + void make_endpoint (db::cell_index_type cell_a, db::cell_index_type cell_b) + { + m_cell_a = std::numeric_limits::max (); + m_trans.clear (); + + m_endpoints_a.insert (std::make_pair (cell_a, m_ep_index)); + m_endpoints_b.insert (std::make_pair (cell_b, m_ep_index)); + ++m_ep_index; } private: const db::Layout &m_layout_a; - db::cell_index_type m_initial_cell_a; - const std::set *m_selection_cone_a; // TODO -> ptr @@@ + const std::set *m_selection_cone_a; const db::Layout &m_layout_b; - db::cell_index_type m_initial_cell_b; - const std::set *m_selection_cone_b; // TODO -> ptr @@@ - std::map m_tsa, m_tsb; - - const trans_set_t &get_trans_set (std::map &ts_cache, const db::Layout &layout, db::cell_index_type initial_cell, const std::set &selection, db::cell_index_type for_cell) + const std::set *m_selection_cone_b; + db::cell_index_type m_cell_a; + std::set m_callers_a; + trans_set_t m_trans; + std::map m_endpoints_a, m_endpoints_b; + unsigned int m_ep_index; + + bool collect_or_compare_trans_set (bool collect, + trans_set_t &ts, + const std::map &endpoints, + const db::Layout &layout, + const std::set &selection, + db::cell_index_type for_cell, + const db::ICplxTrans &child_trans) { - auto tsi = ts_cache.find (for_cell); - if (tsi == ts_cache.end ()) { - tsi = ts_cache.insert (std::make_pair (for_cell, trans_set_t ())).first; - get_trans_set_uncached (tsi->second, ts_cache, layout, initial_cell, selection, for_cell); + auto ep = endpoints.find (for_cell); + if (ep != endpoints.end ()) { + + auto key = std::make_pair (ep->second, child_trans); + + if (collect) { + ts.insert (key); + return true; + } else { + auto i = ts.find (key); + if (i == ts.end () || *i != key) { + return false; + } else { + ts.erase (i); + return true; + } + } + } - return tsi->second; - } - void get_trans_set_uncached (trans_set_t &ts, std::map &ts_cache, const db::Layout &layout, db::cell_index_type initial_cell, const std::set &selection, db::cell_index_type for_cell) - { - if (for_cell == initial_cell) { - ts.insert (db::ICplxTrans ()); - return; + const db::Cell &fc = layout.cell (for_cell); + + std::set selected_parents; + for (auto p = fc.begin_parent_cells (); p != fc.end_parent_cells (); ++p) { + if (selection.find (*p) != selection.end ()) { + selected_parents.insert (*p); + } } - const db::Cell &c = layout.cell (for_cell); - for (auto p = c.begin_parent_insts (); ! p.at_end (); ++p) { + for (auto p = fc.begin_parent_insts (); ! p.at_end (); ++p) { - const db::CellInstArray &inst = p->child_inst ().cell_inst (); db::cell_index_type parent_cell = p->parent_cell_index (); + if (selected_parents.find (parent_cell) != selected_parents.end ()) { - if (selection.find (inst.object ().cell_index ()) != selection.end ()) { - - const trans_set_t &pts = get_trans_set (ts_cache, layout, initial_cell, selection, parent_cell); + const db::CellInstArray &inst = *p->basic_child_inst (); for (auto a = inst.begin (); ! a.at_end (); ++a) { auto ta = inst.complex_trans (*a); - for (auto pt = pts.begin (); pt != pts.end (); ++pt) { - ts.insert (*pt * ta); + if (! collect_or_compare_trans_set (collect, ts, endpoints, layout, selection, parent_cell, ta * child_trans)) { + return false; } - } } } + + return true; } }; + #endif // ------------------------------------------------------------------------------------- @@ -509,12 +589,12 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty db::CellCounter cc_b (&layout_b, cell_index_b); std::multimap cm_b; - for (db::CellCounter::selection_iterator c = cc_b.begin (); c != cc_b.end (); ++c) { + for (auto c = cc_b.begin (); c != cc_b.end (); ++c) { cm_b.insert (std::make_pair (*c == cell_index_b ? 0 : cc_b.weight (*c), *c)); } std::multimap cm_a; - for (db::CellCounter::selection_iterator c = cc_a.begin (); c != cc_a.end (); ++c) { + for (auto c = cc_a.begin (); c != cc_a.end (); ++c) { cm_a.insert (std::make_pair (*c == cell_index_a ? 0 : cc_a.weight (*c), *c)); } @@ -551,10 +631,11 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty unsigned int g = 0; std::map > b_group; std::map b_group_of_cell; + std::map > new_candidates; while (a != cm_a.end () && a->first == w) { - candidates.insert (std::make_pair (a->second, std::vector ())); + new_candidates.insert (std::make_pair (a->second, std::vector ())); std::set groups_taken; @@ -566,7 +647,7 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty if (groups_taken.find (bg->second) == groups_taken.end ()) { if (cmp.compare (a->second, bb->second)) { - candidates [a->second] = b_group [bg->second]; + new_candidates [a->second] = b_group [bg->second]; groups_taken.insert (bg->second); } } @@ -574,7 +655,7 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty } else { if (cmp.compare (a->second, bb->second)) { - candidates [a->second].push_back (bb->second); + new_candidates [a->second].push_back (bb->second); b_group_of_cell.insert (std::make_pair (bb->second, g)); b_group.insert (std::make_pair (g, std::vector ())).first->second.push_back (bb->second); } @@ -598,6 +679,15 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty ++b; } + for (auto c = new_candidates.begin (); c != new_candidates.end (); ++c) { + if (c->second.size () == 1) { + // a single candidate: establish as new endpoint for the comparer + cmp.make_endpoint (c->first, c->second.front ()); + } + } + + candidates.insert (new_candidates.begin (), new_candidates.end ()); + } } diff --git a/src/db/db/dbLayerMapping.h b/src/db/db/dbLayerMapping.h index 9b4c34484..4a01eebcb 100644 --- a/src/db/db/dbLayerMapping.h +++ b/src/db/db/dbLayerMapping.h @@ -97,7 +97,7 @@ class DB_PUBLIC LayerMapping /** * @brief Add a layer mapping * - * @param layer_b The index of the layer in layout_a (the source of the mapping) + * @param layer_b The index of the layer in layout_b (the source of the mapping) * @param layer_a The index of the layer in layout_a (the target of the mapping) */ void map (unsigned int layer_b, unsigned int layer_a) From 22a197169d3716e3b961ebdfe6d90a65ed4443c7 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 5 Feb 2026 22:04:10 +0100 Subject: [PATCH 9/9] Trying to fix Windows builds --- src/db/db/dbCellInstanceSetHasher.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db/db/dbCellInstanceSetHasher.h b/src/db/db/dbCellInstanceSetHasher.h index 8cc1cacf6..9ed4ce5b8 100644 --- a/src/db/db/dbCellInstanceSetHasher.h +++ b/src/db/db/dbCellInstanceSetHasher.h @@ -46,7 +46,7 @@ namespace db class DB_PUBLIC CellInstanceSetHasher { public: - class MatrixHash + class DB_PUBLIC MatrixHash : public IMatrix3d { public: