From c1757c472ebe80ceb0021134a22f29f0feaf0a5f Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 25 Oct 2024 00:36:28 +0200 Subject: [PATCH 01/10] First steps towards fix: locking layout while recursive shape iterator is alive. --- src/db/db/dbRecursiveShapeIterator.cc | 7 +++++++ src/db/db/dbRecursiveShapeIterator.h | 1 + 2 files changed, 8 insertions(+) diff --git a/src/db/db/dbRecursiveShapeIterator.cc b/src/db/db/dbRecursiveShapeIterator.cc index 5c628def1..feff72f32 100644 --- a/src/db/db/dbRecursiveShapeIterator.cc +++ b/src/db/db/dbRecursiveShapeIterator.cc @@ -67,6 +67,7 @@ RecursiveShapeIterator &RecursiveShapeIterator::operator= (const RecursiveShapeI m_box_convert = d.m_box_convert; + m_locker = d.m_locker; m_inst = d.m_inst; m_inst_array = d.m_inst_array; m_empty_cells_cache = d.m_empty_cells_cache; @@ -451,6 +452,8 @@ RecursiveShapeIterator::validate (RecursiveShapeReceiver *receiver) const m_needs_reinit = false; // re-initialize + m_locker = db::LayoutLocker (); + mp_cell = mp_top_cell; m_trans_stack.clear (); m_inst_iterators.clear (); @@ -502,6 +505,10 @@ RecursiveShapeIterator::validate (RecursiveShapeReceiver *receiver) const new_cell (receiver); next_shape (receiver); } + + if (mp_layout) { + m_locker = db::LayoutLocker (const_cast (mp_layout.get ()), true); + } } void diff --git a/src/db/db/dbRecursiveShapeIterator.h b/src/db/db/dbRecursiveShapeIterator.h index 96761a1aa..cf893be88 100644 --- a/src/db/db/dbRecursiveShapeIterator.h +++ b/src/db/db/dbRecursiveShapeIterator.h @@ -854,6 +854,7 @@ private: std::unique_ptr mp_complex_region; db::box_convert m_box_convert; + mutable db::LayoutLocker m_locker; mutable inst_iterator m_inst; mutable inst_array_iterator m_inst_array; mutable std::map m_empty_cells_cache; From bea707a9df6cf026b495939ad94472197c6be4ba Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 25 Oct 2024 00:40:42 +0200 Subject: [PATCH 02/10] Releasing the lock when the iterator is at end - otherwise a finished iterator could still block the layout (potential Ruby GC issue) --- src/db/db/dbRecursiveShapeIterator.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/db/db/dbRecursiveShapeIterator.cc b/src/db/db/dbRecursiveShapeIterator.cc index feff72f32..e3872f485 100644 --- a/src/db/db/dbRecursiveShapeIterator.cc +++ b/src/db/db/dbRecursiveShapeIterator.cc @@ -695,6 +695,12 @@ RecursiveShapeIterator::next (RecursiveShapeReceiver *receiver) next_shape (receiver); } + if (at_end ()) { + // release the layout lock if at end - this way, the shape iterator can be + // held further, without blocking the layout. + m_locker = db::LayoutLocker (); + } + } } From 049c0b73b0897cfa472f89c113a6bab5a581f289 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 25 Oct 2024 23:13:40 +0200 Subject: [PATCH 03/10] Debugging, added tests for new recursive shape iterator. --- src/db/db/dbRecursiveShapeIterator.cc | 33 ++++++---- src/db/db/dbRecursiveShapeIterator.h | 19 +++--- .../dbRecursiveInstanceIteratorTests.cc | 1 + .../dbRecursiveShapeIteratorTests.cc | 66 +++++++++++++++++++ 4 files changed, 95 insertions(+), 24 deletions(-) diff --git a/src/db/db/dbRecursiveShapeIterator.cc b/src/db/db/dbRecursiveShapeIterator.cc index e3872f485..1361983ca 100644 --- a/src/db/db/dbRecursiveShapeIterator.cc +++ b/src/db/db/dbRecursiveShapeIterator.cc @@ -341,7 +341,7 @@ RecursiveShapeIterator::set_global_trans (const cplx_trans_type &tr) { if (m_global_trans != tr) { m_global_trans = tr; - m_needs_reinit = true; + reset (); } } @@ -361,7 +361,7 @@ RecursiveShapeIterator::set_region (const box_type ®ion) { if (m_region != region || mp_complex_region.get () != 0) { init_region (region); - m_needs_reinit = true; + reset (); } } @@ -369,7 +369,7 @@ void RecursiveShapeIterator::set_region (const region_type ®ion) { init_region (region); - m_needs_reinit = true; + reset (); } void @@ -382,7 +382,7 @@ RecursiveShapeIterator::confine_region (const box_type ®ion) } else { init_region (m_region & region); } - m_needs_reinit = true; + reset (); } void @@ -395,7 +395,7 @@ RecursiveShapeIterator::confine_region (const region_type ®ion) } else { init_region (region & region_type (m_region)); } - m_needs_reinit = true; + reset (); } void @@ -405,7 +405,7 @@ RecursiveShapeIterator::set_layer (unsigned int layer) m_has_layers = false; m_layers.clear (); m_layer = layer; - m_needs_reinit = true; + reset (); } } @@ -416,7 +416,7 @@ RecursiveShapeIterator::set_layers (const std::vector &layers) m_has_layers = true; m_layers = layers; m_layer = 0; - m_needs_reinit = true; + reset (); } } @@ -506,7 +506,7 @@ RecursiveShapeIterator::validate (RecursiveShapeReceiver *receiver) const next_shape (receiver); } - if (mp_layout) { + if (mp_layout && ! at_end ()) { m_locker = db::LayoutLocker (const_cast (mp_layout.get ()), true); } } @@ -519,7 +519,7 @@ RecursiveShapeIterator::reset_selection () m_start.clear (); m_stop.clear (); - m_needs_reinit = true; + reset (); } } @@ -534,7 +534,7 @@ RecursiveShapeIterator::unselect_cells (const std::set &cel m_start.erase (*c); } - m_needs_reinit = true; + reset (); } } @@ -549,11 +549,18 @@ RecursiveShapeIterator::unselect_all_cells () m_stop.insert (c->cell_index ()); } - m_needs_reinit = true; + reset (); } } +void +RecursiveShapeIterator::reset () +{ + m_needs_reinit = true; + m_locker = db::LayoutLocker (); +} + void RecursiveShapeIterator::select_cells (const std::set &cells) { @@ -564,7 +571,7 @@ RecursiveShapeIterator::select_cells (const std::set &cells m_stop.erase (*c); } - m_needs_reinit = true; + reset (); } } @@ -579,7 +586,7 @@ RecursiveShapeIterator::select_all_cells () m_start.insert (c->cell_index ()); } - m_needs_reinit = true; + reset (); } } diff --git a/src/db/db/dbRecursiveShapeIterator.h b/src/db/db/dbRecursiveShapeIterator.h index cf893be88..69e79fb1f 100644 --- a/src/db/db/dbRecursiveShapeIterator.h +++ b/src/db/db/dbRecursiveShapeIterator.h @@ -265,7 +265,7 @@ public: { if (m_max_depth != depth) { m_max_depth = depth; - m_needs_reinit = true; + reset (); } } @@ -288,7 +288,7 @@ public: { if (m_min_depth != depth) { m_min_depth = depth; - m_needs_reinit = true; + reset (); } } @@ -433,7 +433,7 @@ public: { if (m_overlapping != f) { m_overlapping = f; - m_needs_reinit = true; + reset (); } } @@ -452,7 +452,7 @@ public: { if (m_for_merged_input != f) { m_for_merged_input = f; - m_needs_reinit = true; + reset (); } } @@ -483,10 +483,7 @@ public: /** * @brief Reset the iterator */ - void reset () - { - m_needs_reinit = true; - } + void reset (); /** * @brief Select cells @@ -559,7 +556,7 @@ public: { if (m_shape_flags != flags) { m_shape_flags = flags; - m_needs_reinit = true; + reset (); } } @@ -596,7 +593,7 @@ public: { if (mp_shape_prop_sel != prop_sel) { mp_shape_prop_sel = prop_sel; - m_needs_reinit = true; + reset (); } } @@ -610,7 +607,7 @@ public: { if (m_shape_inv_prop_sel != inv) { m_shape_inv_prop_sel = inv; - m_needs_reinit = true; + reset (); } } diff --git a/src/db/unit_tests/dbRecursiveInstanceIteratorTests.cc b/src/db/unit_tests/dbRecursiveInstanceIteratorTests.cc index 9671e5a3c..eb06a6967 100644 --- a/src/db/unit_tests/dbRecursiveInstanceIteratorTests.cc +++ b/src/db/unit_tests/dbRecursiveInstanceIteratorTests.cc @@ -789,3 +789,4 @@ TEST(6) "CHILD_CELL_3_1_1@r0 *1 120000,0" ); } + diff --git a/src/db/unit_tests/dbRecursiveShapeIteratorTests.cc b/src/db/unit_tests/dbRecursiveShapeIteratorTests.cc index 6184176db..b143dbee4 100644 --- a/src/db/unit_tests/dbRecursiveShapeIteratorTests.cc +++ b/src/db/unit_tests/dbRecursiveShapeIteratorTests.cc @@ -1780,3 +1780,69 @@ TEST(13_ForMergedPerformance) } } +// layout locking +TEST(14_LayoutLocking) +{ + db::Layout layout; + + layout.insert_layer (0); + + db::Cell &c0 (layout.cell (layout.add_cell ())); + db::Cell &c1 (layout.cell (layout.add_cell ())); + + db::Box b (0, 100, 1000, 1200); + c1.shapes (0).insert (b); + + db::Trans tt; + c0.insert (db::CellInstArray (db::CellInst (c1.cell_index ()), tt)); + c0.insert (db::CellInstArray (db::CellInst (c1.cell_index ()), db::Trans (db::Vector (2000, -2000)))); + + EXPECT_EQ (layout.under_construction (), false); + + db::RecursiveShapeIterator iter (layout, c0, 0); + + EXPECT_EQ (layout.under_construction (), false); + + EXPECT_EQ (iter.at_end (), false); + EXPECT_EQ (layout.under_construction (), true); + + EXPECT_EQ (iter.shape ().to_string (), "box (0,100;1000,1200)"); + EXPECT_EQ (layout.under_construction (), true); + ++iter; + + EXPECT_EQ (iter.at_end (), false); + + EXPECT_EQ (iter.shape ().to_string (), "box (0,100;1000,1200)"); + EXPECT_EQ (layout.under_construction (), true); + ++iter; + + EXPECT_EQ (layout.under_construction (), false); + EXPECT_EQ (iter.at_end (), true); + + // reset will restart + iter.reset (); + + EXPECT_EQ (layout.under_construction (), false); + + EXPECT_EQ (iter.at_end (), false); + EXPECT_EQ (layout.under_construction (), true); + + // a copy will hold the lock + iter.reset (); + + EXPECT_EQ (layout.under_construction (), false); + EXPECT_EQ (iter.at_end (), false); + + EXPECT_EQ (layout.under_construction (), true); + db::RecursiveShapeIterator iter_copy = iter; + + while (! iter.at_end ()) { + ++iter; + } + + EXPECT_EQ (layout.under_construction (), true); + iter_copy = db::RecursiveShapeIterator (); + + EXPECT_EQ (layout.under_construction (), false); +} + From a83e58b8de80f39ff5a55442b6988137121fe830 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 26 Oct 2024 21:21:28 +0200 Subject: [PATCH 04/10] Solving potential thread collision issues upon Layout::update. --- src/db/db/dbLayout.cc | 64 +++++++++++++++++++++------ src/db/db/dbLayout.h | 31 +++++-------- src/db/db/dbLayoutStateModel.cc | 10 +++-- src/db/db/dbLayoutStateModel.h | 2 +- src/db/db/dbRecursiveShapeIterator.cc | 4 +- 5 files changed, 70 insertions(+), 41 deletions(-) diff --git a/src/db/db/dbLayout.cc b/src/db/db/dbLayout.cc index b69ca8d73..327d91415 100644 --- a/src/db/db/dbLayout.cc +++ b/src/db/db/dbLayout.cc @@ -1749,8 +1749,49 @@ Layout::end_top_cells () const return m_top_down_list.begin () + m_top_cells; } +void +Layout::start_changes () +{ + tl::MutexLocker locker (&lock ()); + ++m_invalid; +} + +void +Layout::end_changes () +{ + tl::MutexLocker locker (&lock ()); + if (m_invalid > 0) { + if (--m_invalid == 0) { + force_update_no_lock (); + } + } +} + +void +Layout::end_changes_no_update () +{ + tl::MutexLocker locker (&lock ()); + if (m_invalid > 0) { + --m_invalid; + } +} + void Layout::force_update () +{ + // 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. + if (! hier_dirty () && ! bboxes_dirty ()) { + return; + } + + tl::MutexLocker locker (&lock ()); + force_update_no_lock (); +} + +void +Layout::force_update_no_lock () const { if (hier_dirty () || bboxes_dirty ()) { @@ -1776,22 +1817,17 @@ Layout::force_update () void Layout::update () const { - if (! under_construction () && (hier_dirty () || bboxes_dirty ())) { + // 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. + if (under_construction () || (! hier_dirty () && ! bboxes_dirty ())) { + return; + } - try { - - m_invalid = std::numeric_limits::max (); // prevent recursion - - db::LayoutStateModel *state_model = const_cast ((const db::LayoutStateModel *) this); - state_model->update (); - - m_invalid = 0; - - } catch (...) { - m_invalid = 0; - throw; - } + tl::MutexLocker locker (&lock ()); + if (! under_construction ()) { + force_update_no_lock (); } } diff --git a/src/db/db/dbLayout.h b/src/db/db/dbLayout.h index f5b28cd0f..e7b3d21a3 100644 --- a/src/db/db/dbLayout.h +++ b/src/db/db/dbLayout.h @@ -624,7 +624,7 @@ public: * @brief Gets the lock for the layout object * This is a generic lock that can be used to lock modifications against multiple threads. */ - tl::Mutex &lock () + tl::Mutex &lock () const { return m_lock; } @@ -1791,34 +1791,18 @@ public: * an operation, the start/end_changes method pair does not * need to be called. */ - void start_changes () - { - ++m_invalid; - } + void start_changes (); /** * @brief Cancel the "in changes" state (see "start_changes") */ - void end_changes () - { - if (m_invalid > 0) { - --m_invalid; - if (! m_invalid) { - update (); - } - } - } + void end_changes (); /** * @brief Cancel the "in changes" state (see "start_changes") * This version does not force an update */ - void end_changes_no_update () - { - if (m_invalid > 0) { - --m_invalid; - } - } + void end_changes_no_update (); /** * @brief Tell if the layout object is under construction @@ -2168,7 +2152,7 @@ private: std::map m_meta_info_by_cell; std::string m_tech_name; - tl::Mutex m_lock; + mutable tl::Mutex m_lock; /** * @brief Sort the cells topologically @@ -2211,6 +2195,11 @@ private: * @brief Recovers a proxy without considering the library from context_info */ db::Cell *recover_proxy_no_lib (const LayoutOrCellContextInfo &context_info); + + /** + * @brief Does an update without checking under_construction() and no Mutex + */ + void force_update_no_lock () const; }; /** diff --git a/src/db/db/dbLayoutStateModel.cc b/src/db/db/dbLayoutStateModel.cc index 4bc02d822..80eecfd6c 100644 --- a/src/db/db/dbLayoutStateModel.cc +++ b/src/db/db/dbLayoutStateModel.cc @@ -29,13 +29,14 @@ namespace db { LayoutStateModel::LayoutStateModel (bool busy) - : m_hier_dirty (false), m_hier_generation_id (0), m_all_bboxes_dirty (false), m_busy (busy) + : m_hier_dirty (false), m_hier_generation_id (0), m_all_bboxes_dirty (false), m_some_bboxes_dirty (false), m_busy (busy) { // .. nothing yet .. } LayoutStateModel::LayoutStateModel (const LayoutStateModel &d) - : m_hier_dirty (d.m_hier_dirty), m_hier_generation_id (d.m_hier_generation_id), m_bboxes_dirty (d.m_bboxes_dirty), m_all_bboxes_dirty (d.m_all_bboxes_dirty), m_busy (d.m_busy) + : m_hier_dirty (d.m_hier_dirty), m_hier_generation_id (d.m_hier_generation_id), m_bboxes_dirty (d.m_bboxes_dirty), + m_all_bboxes_dirty (d.m_all_bboxes_dirty), m_some_bboxes_dirty (d.m_some_bboxes_dirty), m_busy (d.m_busy) { // .. nothing yet .. } @@ -47,6 +48,7 @@ LayoutStateModel::operator= (const LayoutStateModel &d) m_hier_generation_id = d.m_hier_generation_id; m_bboxes_dirty = d.m_bboxes_dirty; m_all_bboxes_dirty = d.m_all_bboxes_dirty; + m_some_bboxes_dirty = d.m_some_bboxes_dirty; m_busy = d.m_busy; return *this; } @@ -84,6 +86,7 @@ LayoutStateModel::invalidate_bboxes (unsigned int index) m_bboxes_dirty.resize (index + 1, false); } m_bboxes_dirty [index] = true; + m_some_bboxes_dirty = true; } } } @@ -91,7 +94,7 @@ LayoutStateModel::invalidate_bboxes (unsigned int index) bool LayoutStateModel::bboxes_dirty () const { - return ! m_bboxes_dirty.empty () || m_all_bboxes_dirty; + return m_some_bboxes_dirty || m_all_bboxes_dirty; } void @@ -100,6 +103,7 @@ LayoutStateModel::update () if (bboxes_dirty () || m_hier_dirty) { do_update (); m_bboxes_dirty.clear (); + m_some_bboxes_dirty = false; m_all_bboxes_dirty = false; m_hier_dirty = false; } diff --git a/src/db/db/dbLayoutStateModel.h b/src/db/db/dbLayoutStateModel.h index 281298b74..071d30eba 100644 --- a/src/db/db/dbLayoutStateModel.h +++ b/src/db/db/dbLayoutStateModel.h @@ -209,7 +209,7 @@ private: bool m_hier_dirty; size_t m_hier_generation_id; std::vector m_bboxes_dirty; - bool m_all_bboxes_dirty; + bool m_all_bboxes_dirty, m_some_bboxes_dirty; bool m_busy; void do_invalidate_hier (); diff --git a/src/db/db/dbRecursiveShapeIterator.cc b/src/db/db/dbRecursiveShapeIterator.cc index 1361983ca..27d86e2aa 100644 --- a/src/db/db/dbRecursiveShapeIterator.cc +++ b/src/db/db/dbRecursiveShapeIterator.cc @@ -703,8 +703,8 @@ RecursiveShapeIterator::next (RecursiveShapeReceiver *receiver) } if (at_end ()) { - // release the layout lock if at end - this way, the shape iterator can be - // held further, without blocking the layout. + // Take this opportunity the release the layout lock. + // This way, the shape iterator can be held further, without blocking the layout. m_locker = db::LayoutLocker (); } From 0c0baed664a41d24580a9880dfca420f10a00581 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 26 Oct 2024 21:24:01 +0200 Subject: [PATCH 05/10] Avoiding a layout lock on Region::empty. --- src/db/db/dbOriginalLayerRegion.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/db/db/dbOriginalLayerRegion.cc b/src/db/db/dbOriginalLayerRegion.cc index 44f22c604..a4c15e76c 100644 --- a/src/db/db/dbOriginalLayerRegion.cc +++ b/src/db/db/dbOriginalLayerRegion.cc @@ -330,7 +330,11 @@ OriginalLayerRegion::begin_merged_iter () const bool OriginalLayerRegion::empty () const { - return m_iter.at_end (); + // NOTE: we should to make sure the iterator isn't validated as this would spoil the usability or OriginalLayerRegion upon + // layout changes + db::RecursiveShapeIterator iter = m_iter; + + return iter.at_end (); } bool From 57e98d17ac8a3ff99cc07fc679c8d22461d0fe40 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 26 Oct 2024 21:24:51 +0200 Subject: [PATCH 06/10] Fixed some Ruby tests - as RecursiveShapeIterator lifelime may be longer than expected, we need to explicitly destroy the iterator now to avoid layout locking. --- testdata/ruby/dbLayoutTests1.rb | 94 ++++++++++++++++++--------------- testdata/ruby/dbPCells.rb | 68 ++++++++++++++---------- 2 files changed, 91 insertions(+), 71 deletions(-) diff --git a/testdata/ruby/dbLayoutTests1.rb b/testdata/ruby/dbLayoutTests1.rb index b0693188d..a545423e5 100644 --- a/testdata/ruby/dbLayoutTests1.rb +++ b/testdata/ruby/dbLayoutTests1.rb @@ -232,6 +232,16 @@ class DBLayoutTests1_TestClass < TestBase end + def first_shape(s) + + sdup = s.dup + shape = sdup.shape + sdup._destroy + + return shape + + end + def collect(s, l) res = [] @@ -1042,18 +1052,18 @@ class DBLayoutTests1_TestClass < TestBase c0c = l.cell(l.add_cell("c0")) c0c.copy_shapes(c0) assert_equal(collect(c0c.begin_shapes_rec(0), l), "[c0$1](0,100;1000,1200)") - assert_equal(c0c.begin_shapes_rec(0).shape.property("p"), nil) + assert_equal(first_shape(c0c.begin_shapes_rec(0)).property("p"), nil) assert_equal(collect(c0c.begin_shapes_rec(1), l), "[c0$1](1,101;1001,1201)") - assert_equal(c0c.begin_shapes_rec(1).shape.property("p"), 17) + assert_equal(first_shape(c0c.begin_shapes_rec(1)).property("p"), 17) c0c.clear lm = RBA::LayerMapping::new lm.map(1, 0) c0c.copy_shapes(c0, lm) assert_equal(collect(c0c.begin_shapes_rec(0), l), "[c0$1](1,101;1001,1201)") - assert_equal(c0c.begin_shapes_rec(0).shape.property("p"), 17) + assert_equal(first_shape(c0c.begin_shapes_rec(0)).property("p"), 17) assert_equal(collect(c0c.begin_shapes_rec(1), l), "") - assert_equal(c0c.begin_shapes_rec(1).shape.property("p"), nil) + assert_equal(first_shape(c0c.begin_shapes_rec(1)).property("p"), nil) l2 = RBA::Layout::new l2.dbu = 0.0005 @@ -1063,9 +1073,9 @@ class DBLayoutTests1_TestClass < TestBase layer1 = l2.find_layer(1, 0) layer2 = l2.find_layer(2, 0) assert_equal(collect(c0c.begin_shapes_rec(layer1), l), "[c0](0,200;2000,2400)") - assert_equal(c0c.begin_shapes_rec(layer1).shape.property("p"), nil) + assert_equal(first_shape(c0c.begin_shapes_rec(layer1)).property("p"), nil) assert_equal(collect(c0c.begin_shapes_rec(layer2), l), "[c0](2,202;2002,2402)") - assert_equal(c0c.begin_shapes_rec(layer2).shape.property("p"), 17) + assert_equal(first_shape(c0c.begin_shapes_rec(layer2)).property("p"), 17) l2 = RBA::Layout::new l2.dbu = 0.0005 @@ -1084,9 +1094,9 @@ class DBLayoutTests1_TestClass < TestBase layer1 = l2.find_layer(1, 0) layer2 = l2.find_layer(2, 0) assert_equal(collect(c0c.begin_shapes_rec(layer1), l), "[c0](0,200;2000,2400)") - assert_equal(c0c.begin_shapes_rec(layer1).shape.property("p"), nil) + assert_equal(first_shape(c0c.begin_shapes_rec(layer1)).property("p"), nil) assert_equal(collect(c0c.begin_shapes_rec(layer2), l), "[c0](2,202;2002,2402)") - assert_equal(c0c.begin_shapes_rec(layer2).shape.property("p"), 17) + assert_equal(first_shape(c0c.begin_shapes_rec(layer2)).property("p"), 17) end @@ -1127,9 +1137,9 @@ class DBLayoutTests1_TestClass < TestBase assert_equal(collect(c0.begin_shapes_rec(0), l), "[c1](0,100;1000,1200)/[c2](100,0;1100,1100)/[c3](1200,0;2200,1100)/[c3](-1200,0;-100,1000)") assert_equal(collect(c0.begin_shapes_rec(1), l), "") assert_equal(collect(c0c.begin_shapes_rec(0), l), "[c0$1](0,100;1000,1200)") - assert_equal(c0c.begin_shapes_rec(0).shape.property("p"), nil) + assert_equal(first_shape(c0c.begin_shapes_rec(0)).property("p"), nil) assert_equal(collect(c0c.begin_shapes_rec(1), l), "[c0$1](1,101;1001,1201)") - assert_equal(c0c.begin_shapes_rec(1).shape.property("p"), 17) + assert_equal(first_shape(c0c.begin_shapes_rec(1)).property("p"), 17) c0.move_shapes(c0c) assert_equal(collect(c0.begin_shapes_rec(0), l), "[c0](0,100;1000,1200)/[c1](0,100;1000,1200)/[c2](100,0;1100,1100)/[c3](1200,0;2200,1100)/[c3](-1200,0;-100,1000)") assert_equal(collect(c0.begin_shapes_rec(1), l), "[c0](1,101;1001,1201)") @@ -1142,9 +1152,9 @@ class DBLayoutTests1_TestClass < TestBase assert_equal(collect(c0.begin_shapes_rec(0), l), "[c0](0,100;1000,1200)/[c1](0,100;1000,1200)/[c2](100,0;1100,1100)/[c3](1200,0;2200,1100)/[c3](-1200,0;-100,1000)") assert_equal(collect(c0.begin_shapes_rec(1), l), "") assert_equal(collect(c0c.begin_shapes_rec(0), l), "[c0$1](1,101;1001,1201)") - assert_equal(c0c.begin_shapes_rec(0).shape.property("p"), 17) + assert_equal(first_shape(c0c.begin_shapes_rec(0)).property("p"), 17) assert_equal(collect(c0c.begin_shapes_rec(1), l), "") - assert_equal(c0c.begin_shapes_rec(1).shape.property("p"), nil) + assert_equal(first_shape(c0c.begin_shapes_rec(1)).property("p"), nil) l = ll.dup c0 = l.cell("c0") @@ -1159,9 +1169,9 @@ class DBLayoutTests1_TestClass < TestBase assert_equal(collect(c0.begin_shapes_rec(0), l), "[c1](0,100;1000,1200)/[c2](100,0;1100,1100)/[c3](1200,0;2200,1100)/[c3](-1200,0;-100,1000)") assert_equal(collect(c0.begin_shapes_rec(1), l), "") assert_equal(collect(c0c.begin_shapes_rec(layer1), l), "[c0](0,200;2000,2400)") - assert_equal(c0c.begin_shapes_rec(layer1).shape.property("p"), nil) + assert_equal(first_shape(c0c.begin_shapes_rec(layer1)).property("p"), nil) assert_equal(collect(c0c.begin_shapes_rec(layer2), l), "[c0](2,202;2002,2402)") - assert_equal(c0c.begin_shapes_rec(layer2).shape.property("p"), 17) + assert_equal(first_shape(c0c.begin_shapes_rec(layer2)).property("p"), 17) l = ll.dup c0 = l.cell("c0") @@ -1185,9 +1195,9 @@ class DBLayoutTests1_TestClass < TestBase assert_equal(collect(c0.begin_shapes_rec(0), l), "[c1](0,100;1000,1200)/[c2](100,0;1100,1100)/[c3](1200,0;2200,1100)/[c3](-1200,0;-100,1000)") assert_equal(collect(c0.begin_shapes_rec(1), l), "") assert_equal(collect(c0c.begin_shapes_rec(layer1), l), "[c0](0,200;2000,2400)") - assert_equal(c0c.begin_shapes_rec(layer1).shape.property("p"), nil) + assert_equal(first_shape(c0c.begin_shapes_rec(layer1)).property("p"), nil) assert_equal(collect(c0c.begin_shapes_rec(layer2), l), "[c0](2,202;2002,2402)") - assert_equal(c0c.begin_shapes_rec(layer2).shape.property("p"), 17) + assert_equal(first_shape(c0c.begin_shapes_rec(layer2)).property("p"), 17) end @@ -1351,7 +1361,7 @@ class DBLayoutTests1_TestClass < TestBase layer1 = l2.find_layer(1, 0) layer2 = l2.find_layer(2, 0) - assert_equal(l2.cell("c1").begin_shapes_rec(layer1).shape.property("p"), 17) + assert_equal(first_shape(l2.cell("c1").begin_shapes_rec(layer1)).property("p"), 17) assert_equal(collect(c0c.begin_shapes_rec(layer1), l2), "[c0](0,200;2000,2400)/[c2](200,0;2200,2200)/[c3](2400,0;4400,2200)/[c3](-2400,0;-200,2000)/[c1](0,200;2000,2400)") assert_equal(collect(c0c.begin_shapes_rec(layer2), l2), "[c0](2,202;2002,2402)") @@ -1401,7 +1411,7 @@ class DBLayoutTests1_TestClass < TestBase i0 = nil c0c.each_inst { |i| i.cell_index == l.cell("c1$1").cell_index && i0 = i } assert_equal(i0.property("p"), 18) - assert_equal(l.cell("c1$1").begin_shapes_rec(0).shape.property("p"), 17) + assert_equal(first_shape(l.cell("c1$1").begin_shapes_rec(0)).property("p"), 17) assert_equal(collect(c0.begin_shapes_rec(0), l), "") assert_equal(collect(c0.begin_shapes_rec(1), l), "") @@ -1429,7 +1439,7 @@ class DBLayoutTests1_TestClass < TestBase layer1 = l2.find_layer(1, 0) layer2 = l2.find_layer(2, 0) - assert_equal(l2.cell("c1").begin_shapes_rec(layer1).shape.property("p"), 17) + assert_equal(first_shape(l2.cell("c1").begin_shapes_rec(layer1)).property("p"), 17) assert_equal(collect(c0c.begin_shapes_rec(layer1), l2), "[c0](0,200;2000,2400)/[c2](200,0;2200,2200)/[c3](2400,0;4400,2200)/[c3](-2400,0;-200,2000)/[c1](0,200;2000,2400)") assert_equal(collect(c0c.begin_shapes_rec(layer2), l2), "[c0](2,202;2002,2402)") @@ -1471,16 +1481,16 @@ class DBLayoutTests1_TestClass < TestBase cm.for_single_cell(l, c0c.cell_index, l, c0.cell_index) c0c.copy_tree_shapes(c0, cm) assert_equal(collect(c0c.begin_shapes_rec(0), l), "[c0$1](0,100;1000,1200)/[c0$1](0,100;1000,1200)/[c0$1](100,0;1100,1100)/[c0$1](-1200,0;-100,1000)/[c0$1](1200,0;2200,1100)") - assert_equal(c0c.begin_shapes_rec(0).shape.property("p"), nil) + assert_equal(first_shape(c0c.begin_shapes_rec(0)).property("p"), nil) assert_equal(collect(c0c.begin_shapes_rec(1), l), "[c0$1](1,101;1001,1201)") - assert_equal(c0c.begin_shapes_rec(1).shape.property("p"), 17) + assert_equal(first_shape(c0c.begin_shapes_rec(1)).property("p"), 17) c0c.clear lm = RBA::LayerMapping::new lm.map(1, 0) c0c.copy_tree_shapes(c0, cm, lm) assert_equal(collect(c0c.begin_shapes_rec(0), l), "[c0$1](1,101;1001,1201)") - assert_equal(c0c.begin_shapes_rec(0).shape.property("p"), 17) + assert_equal(first_shape(c0c.begin_shapes_rec(0)).property("p"), 17) assert_equal(collect(c0c.begin_shapes_rec(1), l), "") assert_equal(c0c.begin_shapes_rec(1).shape.property("p"), nil) @@ -1488,9 +1498,9 @@ class DBLayoutTests1_TestClass < TestBase cm.for_single_cell_full(l, c0c.cell_index, l, c0.cell_index) c0c.copy_tree_shapes(c0, cm) assert_equal(collect(c0c.begin_shapes_rec(0), l), "[c0$1](0,100;1000,1200)/[c2$1](100,0;1100,1100)/[c3$1](1200,0;2200,1100)/[c3$1](-1200,0;-100,1000)/[c1$1](0,100;1000,1200)") - assert_equal(c0c.begin_shapes_rec(0).shape.property("p"), nil) + assert_equal(first_shape(c0c.begin_shapes_rec(0)).property("p"), nil) assert_equal(collect(c0c.begin_shapes_rec(1), l), "[c0$1](1,101;1001,1201)") - assert_equal(c0c.begin_shapes_rec(1).shape.property("p"), 17) + assert_equal(first_shape(c0c.begin_shapes_rec(1)).property("p"), 17) i0 = nil c0c.each_inst { |i| i.cell_index == l.cell("c1$1").cell_index && i0 = i } @@ -1505,9 +1515,9 @@ class DBLayoutTests1_TestClass < TestBase layer1 = l2.find_layer(1, 0) layer2 = l2.find_layer(2, 0) assert_equal(collect(c0c.begin_shapes_rec(layer1), l), "[c0](0,200;2000,2400)/[c2](200,0;2200,2200)/[c3](2400,0;4400,2200)/[c3](-2400,0;-200,2000)/[c1](0,200;2000,2400)") - assert_equal(c0c.begin_shapes_rec(layer1).shape.property("p"), nil) + assert_equal(first_shape(c0c.begin_shapes_rec(layer1)).property("p"), nil) assert_equal(collect(c0c.begin_shapes_rec(layer2), l), "[c0](2,202;2002,2402)") - assert_equal(c0c.begin_shapes_rec(layer2).shape.property("p"), 17) + assert_equal(first_shape(c0c.begin_shapes_rec(layer2)).property("p"), 17) i0 = nil c0c.each_inst { |i| i.cell_index == l2.cell("c1").cell_index && i0 = i } @@ -1559,9 +1569,9 @@ class DBLayoutTests1_TestClass < TestBase assert_equal(collect(c0.begin_shapes_rec(0), l), "") assert_equal(collect(c0.begin_shapes_rec(1), l), "") assert_equal(collect(c0c.begin_shapes_rec(0), l), "[c0$1](0,100;1000,1200)/[c0$1](0,100;1000,1200)/[c0$1](100,0;1100,1100)/[c0$1](-1200,0;-100,1000)/[c0$1](1200,0;2200,1100)") - assert_equal(c0c.begin_shapes_rec(0).shape.property("p"), nil) + assert_equal(first_shape(c0c.begin_shapes_rec(0)).property("p"), nil) assert_equal(collect(c0c.begin_shapes_rec(1), l), "[c0$1](1,101;1001,1201)") - assert_equal(c0c.begin_shapes_rec(1).shape.property("p"), 17) + assert_equal(first_shape(c0c.begin_shapes_rec(1)).property("p"), 17) l = ll.dup c0 = l.cell("c0") @@ -1573,9 +1583,9 @@ class DBLayoutTests1_TestClass < TestBase assert_equal(collect(c0.begin_shapes_rec(0), l), "[c0](0,100;1000,1200)/[c2](100,0;1100,1100)/[c3](1200,0;2200,1100)/[c3](-1200,0;-100,1000)/[c1](0,100;1000,1200)") assert_equal(collect(c0.begin_shapes_rec(1), l), "") assert_equal(collect(c0c.begin_shapes_rec(0), l), "[c0$1](1,101;1001,1201)") - assert_equal(c0c.begin_shapes_rec(0).shape.property("p"), 17) + assert_equal(first_shape(c0c.begin_shapes_rec(0)).property("p"), 17) assert_equal(collect(c0c.begin_shapes_rec(1), l), "") - assert_equal(c0c.begin_shapes_rec(1).shape.property("p"), nil) + assert_equal(first_shape(c0c.begin_shapes_rec(1)).property("p"), nil) l = ll.dup c0 = l.cell("c0") @@ -1586,9 +1596,9 @@ class DBLayoutTests1_TestClass < TestBase assert_equal(collect(c0.begin_shapes_rec(0), l), "") assert_equal(collect(c0.begin_shapes_rec(1), l), "") assert_equal(collect(c0c.begin_shapes_rec(0), l), "[c0$1](0,100;1000,1200)/[c2$1](100,0;1100,1100)/[c3$1](1200,0;2200,1100)/[c3$1](-1200,0;-100,1000)/[c1$1](0,100;1000,1200)") - assert_equal(c0c.begin_shapes_rec(0).shape.property("p"), nil) + assert_equal(first_shape(c0c.begin_shapes_rec(0)).property("p"), nil) assert_equal(collect(c0c.begin_shapes_rec(1), l), "[c0$1](1,101;1001,1201)") - assert_equal(c0c.begin_shapes_rec(1).shape.property("p"), 17) + assert_equal(first_shape(c0c.begin_shapes_rec(1)).property("p"), 17) i0 = nil c0c.each_inst { |i| i.cell_index == l.cell("c1$1").cell_index && i0 = i } @@ -1608,9 +1618,9 @@ class DBLayoutTests1_TestClass < TestBase layer1 = l2.find_layer(1, 0) layer2 = l2.find_layer(2, 0) assert_equal(collect(c0c.begin_shapes_rec(layer1), l), "[c0](0,200;2000,2400)/[c2](200,0;2200,2200)/[c3](2400,0;4400,2200)/[c3](-2400,0;-200,2000)/[c1](0,200;2000,2400)") - assert_equal(c0c.begin_shapes_rec(layer1).shape.property("p"), nil) + assert_equal(first_shape(c0c.begin_shapes_rec(layer1)).property("p"), nil) assert_equal(collect(c0c.begin_shapes_rec(layer2), l), "[c0](2,202;2002,2402)") - assert_equal(c0c.begin_shapes_rec(layer2).shape.property("p"), 17) + assert_equal(first_shape(c0c.begin_shapes_rec(layer2)).property("p"), 17) i0 = nil c0c.each_inst { |i| i.cell_index == l2.cell("c1").cell_index && i0 = i } @@ -1833,10 +1843,10 @@ class DBLayoutTests1_TestClass < TestBase lt.copy_tree_shapes(l, cm) assert_equal(collect(c0t.begin_shapes_rec(0), lt), "[c0](0,100;1000,1200)/[c0](0,100;1000,1200)/[c0](100,0;1100,1100)/[c0](-1200,0;-100,1000)/[c0](1200,0;2200,1100)") - assert_equal(c0t.begin_shapes_rec(0).shape.property("p"), nil) + assert_equal(first_shape(c0t.begin_shapes_rec(0)).property("p"), nil) assert_equal(collect(c9t.begin_shapes_rec(0), lt), "[c9](0,100;1000,1200)") assert_equal(collect(c0t.begin_shapes_rec(1), l), "[c0](1,101;1001,1201)") - assert_equal(c0t.begin_shapes_rec(1).shape.property("p"), 17) + assert_equal(first_shape(c0t.begin_shapes_rec(1)).property("p"), 17) assert_equal(collect(c9t.begin_shapes_rec(1), l), "") lt = RBA::Layout::new @@ -1850,10 +1860,10 @@ class DBLayoutTests1_TestClass < TestBase lt.copy_tree_shapes(l, cm) assert_equal(collect(c0t.begin_shapes_rec(0), lt), "[c0](0,100;1000,1200)/[c2](100,0;1100,1100)/[c3](1200,0;2200,1100)/[c3](-1200,0;-100,1000)/[c1](0,100;1000,1200)") - assert_equal(c0t.begin_shapes_rec(0).shape.property("p"), nil) + assert_equal(first_shape(c0t.begin_shapes_rec(0)).property("p"), nil) assert_equal(collect(c9t.begin_shapes_rec(0), lt), "[c1](0,100;1000,1200)") assert_equal(collect(c0t.begin_shapes_rec(1), l), "[c0](1,101;1001,1201)") - assert_equal(c0t.begin_shapes_rec(1).shape.property("p"), 17) + assert_equal(first_shape(c0t.begin_shapes_rec(1)).property("p"), 17) assert_equal(collect(c9t.begin_shapes_rec(1), l), "") lt = RBA::Layout::new @@ -1873,7 +1883,7 @@ class DBLayoutTests1_TestClass < TestBase lt.copy_tree_shapes(l, cm, lm) assert_equal(collect(c0t.begin_shapes_rec(ll), lt), "[c0](0,100;1000,1200)/[c2](100,0;1100,1100)/[c3](1200,0;2200,1100)/[c3](-1200,0;-100,1000)/[c1](0,100;1000,1200)") - assert_equal(c0t.begin_shapes_rec(ll).shape.property("p"), nil) + assert_equal(first_shape(c0t.begin_shapes_rec(ll)).property("p"), nil) assert_equal(collect(c9t.begin_shapes_rec(ll), lt), "[c1](0,100;1000,1200)") lt = RBA::Layout::new @@ -1897,10 +1907,10 @@ class DBLayoutTests1_TestClass < TestBase assert_equal(collect(ls.cell(c9.cell_index).begin_shapes_rec(1), ls), "") assert_equal(collect(c0t.begin_shapes_rec(0), lt), "[c0](0,100;1000,1200)/[c0](0,100;1000,1200)/[c0](100,0;1100,1100)/[c0](-1200,0;-100,1000)/[c0](1200,0;2200,1100)") - assert_equal(c0t.begin_shapes_rec(0).shape.property("p"), nil) + assert_equal(first_shape(c0t.begin_shapes_rec(0)).property("p"), nil) assert_equal(collect(c9t.begin_shapes_rec(0), lt), "[c9](0,100;1000,1200)") assert_equal(collect(c0t.begin_shapes_rec(1), l), "[c0](1,101;1001,1201)") - assert_equal(c0t.begin_shapes_rec(1).shape.property("p"), 17) + assert_equal(first_shape(c0t.begin_shapes_rec(1)).property("p"), 17) assert_equal(collect(c9t.begin_shapes_rec(1), l), "") lt = RBA::Layout::new @@ -1931,7 +1941,7 @@ class DBLayoutTests1_TestClass < TestBase assert_equal(collect(ls.cell(c9.cell_index).begin_shapes_rec(1), ls), "") assert_equal(collect(c0t.begin_shapes_rec(ll), lt), "[c0](0,100;1000,1200)/[c0](0,100;1000,1200)/[c0](100,0;1100,1100)/[c0](-1200,0;-100,1000)/[c0](1200,0;2200,1100)") - assert_equal(c0t.begin_shapes_rec(ll).shape.property("p"), nil) + assert_equal(first_shape(c0t.begin_shapes_rec(ll)).property("p"), nil) assert_equal(collect(c9t.begin_shapes_rec(ll), lt), "[c9](0,100;1000,1200)") end diff --git a/testdata/ruby/dbPCells.rb b/testdata/ruby/dbPCells.rb index 0c192726c..7cdd59a14 100644 --- a/testdata/ruby/dbPCells.rb +++ b/testdata/ruby/dbPCells.rb @@ -23,6 +23,16 @@ end load("test_prologue.rb") +def first_shape(s) + + sdup = s.dup + shape = sdup.shape + sdup._destroy + + return shape + +end + class BoxPCell < RBA::PCellDeclaration def display_text(parameters) @@ -428,30 +438,30 @@ class DBPCell_TestClass < TestBase li2 = ly.layer_indices.find { |li| ly.get_info(li).to_s == "10/0" } assert_equal(li2 != nil, true) - assert_equal(ly.begin_shapes(c1.cell_index, li1).shape.to_s, "box (-250,-500;250,500)") - assert_equal(ly.begin_shapes(lib_proxy.cell_index, li2).shape.to_s, "box (0,0;10,20)") + assert_equal(first_shape(ly.begin_shapes(c1.cell_index, li1)).to_s, "box (-250,-500;250,500)") + assert_equal(first_shape(ly.begin_shapes(lib_proxy.cell_index, li2)).to_s, "box (0,0;10,20)") param = { "w" => 1, "h" => 2 } c1.change_pcell_parameters(pcell_inst, param) - assert_equal(ly.begin_shapes(c1.cell_index, li1).shape.to_s, "box (-50,-100;50,100)") + assert_equal(first_shape(ly.begin_shapes(c1.cell_index, li1)).to_s, "box (-50,-100;50,100)") param = [ RBA::LayerInfo::new(1, 0), 5.0, 5.0 ] c1.change_pcell_parameters(pcell_inst, param) - assert_equal(ly.begin_shapes(c1.cell_index, li1).shape.to_s, "box (-250,-250;250,250)") + assert_equal(first_shape(ly.begin_shapes(c1.cell_index, li1)).to_s, "box (-250,-250;250,250)") pcell_inst.change_pcell_parameters({ "w" => 2.0, "h" => 10.0 }) - assert_equal(ly.begin_shapes(c1.cell_index, li1).shape.to_s, "box (-100,-500;100,500)") + assert_equal(first_shape(ly.begin_shapes(c1.cell_index, li1)).to_s, "box (-100,-500;100,500)") pcell_inst.change_pcell_parameters([ RBA::LayerInfo::new(1, 0), 5.0, 5.0 ]) - assert_equal(ly.begin_shapes(c1.cell_index, li1).shape.to_s, "box (-250,-250;250,250)") + assert_equal(first_shape(ly.begin_shapes(c1.cell_index, li1)).to_s, "box (-250,-250;250,250)") pcell_inst.change_pcell_parameter("w", 5.0) pcell_inst.change_pcell_parameter("h", 1.0) - assert_equal(ly.begin_shapes(c1.cell_index, li1).shape.to_s, "box (-250,-50;250,50)") + assert_equal(first_shape(ly.begin_shapes(c1.cell_index, li1)).to_s, "box (-250,-50;250,50)") c1.change_pcell_parameter(pcell_inst, "w", 10.0) c1.change_pcell_parameter(pcell_inst, "h", 2.0) - assert_equal(ly.begin_shapes(c1.cell_index, li1).shape.to_s, "box (-500,-100;500,100)") + assert_equal(first_shape(ly.begin_shapes(c1.cell_index, li1)).to_s, "box (-500,-100;500,100)") assert_equal(ly.cell(pcell_inst.cell_index).is_pcell_variant?, true) assert_equal(pcell_inst.is_pcell?, true) @@ -460,18 +470,18 @@ class DBPCell_TestClass < TestBase assert_equal(ly.cell(new_id).is_pcell_variant?, false) param = [ RBA::LayerInfo::new(1, 0), 5.0, 5.0 ] c1.change_pcell_parameters(pcell_inst, param) - assert_equal(ly.begin_shapes(c1.cell_index, li1).shape.to_s, "box (-250,-250;250,250)") + assert_equal(first_shape(ly.begin_shapes(c1.cell_index, li1)).to_s, "box (-250,-250;250,250)") pcell_inst.cell_index = new_id - assert_equal(ly.begin_shapes(c1.cell_index, li1).shape.to_s, "box (-500,-100;500,100)") + assert_equal(first_shape(ly.begin_shapes(c1.cell_index, li1)).to_s, "box (-500,-100;500,100)") l10 = ly.layer(10, 0) c1.shapes(l10).insert(RBA::Box::new(0, 10, 100, 210)) l11 = ly.layer(11, 0) c1.shapes(l11).insert(RBA::Text::new("hello", RBA::Trans::new)) - assert_equal(pcell_decl.can_create_from_shape(ly, ly.begin_shapes(c1.cell_index(), l11).shape(), l10), false) - assert_equal(pcell_decl.can_create_from_shape(ly, ly.begin_shapes(c1.cell_index(), l10).shape(), l10), true) - assert_equal(pcell_decl.parameters_from_shape(ly, ly.begin_shapes(c1.cell_index(), l10).shape(), l10).inspect, "[<10/0>, 1.0, 2.0]") - assert_equal(pcell_decl.transformation_from_shape(ly, ly.begin_shapes(c1.cell_index(), l10).shape(), l10).to_s, "r0 50,110") + assert_equal(pcell_decl.can_create_from_shape(ly, first_shape(ly.begin_shapes(c1.cell_index(), l11)), l10), false) + assert_equal(pcell_decl.can_create_from_shape(ly, first_shape(ly.begin_shapes(c1.cell_index(), l10)), l10), true) + assert_equal(pcell_decl.parameters_from_shape(ly, first_shape(ly.begin_shapes(c1.cell_index(), l10)), l10).inspect, "[<10/0>, 1.0, 2.0]") + assert_equal(pcell_decl.transformation_from_shape(ly, first_shape(ly.begin_shapes(c1.cell_index(), l10)), l10).to_s, "r0 50,110") ly._destroy tl._destroy @@ -522,21 +532,21 @@ class DBPCell_TestClass < TestBase pcell_inst.change_pcell_parameter("height", 2.0) assert_equal(norm_hash(pcell_inst.pcell_parameters_by_name()), "{\"height\"=>2.0, \"layer\"=><1/0>, \"secret\"=>0, \"width\"=>1.0}") - assert_equal(ly.begin_shapes(c1.cell_index(), li1).shape().to_s, "box (-50,-100;50,100)") + assert_equal(first_shape(ly.begin_shapes(c1.cell_index(), li1)).to_s, "box (-50,-100;50,100)") param = { "layer" => RBA::LayerInfo::new(2, 0), "width" => 2, "height" => 1 } li2 = ly.layer(2, 0) c1.change_pcell_parameters(pcell_inst, param) - assert_equal(ly.begin_shapes(c1.cell_index(), li2).shape().to_s, "box (-100,-50;100,50)") + assert_equal(first_shape(ly.begin_shapes(c1.cell_index(), li2)).to_s, "box (-100,-50;100,50)") l10 = ly.layer(10, 0) c1.shapes(l10).insert(RBA::Box::new(0, 10, 100, 210)) l11 = ly.layer(11, 0) c1.shapes(l11).insert(RBA::Text::new("hello", RBA::Trans::new)) - assert_equal(pcell_decl.can_create_from_shape(ly, ly.begin_shapes(c1.cell_index(), l11).shape(), l10), false) - assert_equal(pcell_decl.can_create_from_shape(ly, ly.begin_shapes(c1.cell_index(), l10).shape(), l10), true) - assert_equal(pcell_decl.parameters_from_shape(ly, ly.begin_shapes(c1.cell_index(), l10).shape(), l10).inspect, "[<10/0>, 1.0, 2.0, 0]") - assert_equal(pcell_decl.transformation_from_shape(ly, ly.begin_shapes(c1.cell_index(), l10).shape(), l10).to_s, "r0 50,110") + assert_equal(pcell_decl.can_create_from_shape(ly, first_shape(ly.begin_shapes(c1.cell_index(), l11)), l10), false) + assert_equal(pcell_decl.can_create_from_shape(ly, first_shape(ly.begin_shapes(c1.cell_index(), l10)), l10), true) + assert_equal(pcell_decl.parameters_from_shape(ly, first_shape(ly.begin_shapes(c1.cell_index(), l10)), l10).inspect, "[<10/0>, 1.0, 2.0, 0]") + assert_equal(pcell_decl.transformation_from_shape(ly, first_shape(ly.begin_shapes(c1.cell_index(), l10)), l10).to_s, "r0 50,110") param2 = c1.pcell_parameters(pcell_inst) param2[3] = -2 # "secret" @@ -594,13 +604,13 @@ class DBPCell_TestClass < TestBase assert_equal(pcell_inst.is_pcell?, true) - assert_equal(ly.begin_shapes(pcell_inst.cell_index, li1).shape.to_s, "box (-500,-100;500,100)") + assert_equal(first_shape(ly.begin_shapes(pcell_inst.cell_index, li1)).to_s, "box (-500,-100;500,100)") pcell_inst.convert_to_static assert_equal(pcell_inst.is_pcell?, false) - assert_equal(ly.begin_shapes(pcell_inst.cell_index, li1).shape.to_s, "box (-500,-100;500,100)") + assert_equal(first_shape(ly.begin_shapes(pcell_inst.cell_index, li1)).to_s, "box (-500,-100;500,100)") pcell_inst.convert_to_static assert_equal(pcell_inst.is_pcell?, false) - assert_equal(ly.begin_shapes(pcell_inst.cell_index, li1).shape.to_s, "box (-500,-100;500,100)") + assert_equal(first_shape(ly.begin_shapes(pcell_inst.cell_index, li1)).to_s, "box (-500,-100;500,100)") ly._destroy tl._destroy @@ -625,7 +635,7 @@ class DBPCell_TestClass < TestBase pcell_var = ly.cell(pcell_var_id) pcell_inst = c1.insert(RBA::CellInstArray::new(pcell_var_id, RBA::Trans::new)) - assert_equal(ly.begin_shapes(c1.cell_index, ly.layer(1, 0)).shape.to_s, "box (-200,-400;200,400)") + assert_equal(first_shape(ly.begin_shapes(c1.cell_index, ly.layer(1, 0))).to_s, "box (-200,-400;200,400)") ly._destroy tl._destroy @@ -643,7 +653,7 @@ class DBPCell_TestClass < TestBase param = { "w" => 4.0, "h" => 8.0, "l" => RBA::LayerInfo::new(1, 0) } pcell_var_id = lib.layout.add_pcell_variant(pcell_decl_id, param) - assert_equal(lib.layout.begin_shapes(pcell_var_id, lib.layout.layer(1, 0)).shape.to_s, "box (-2000,-4000;2000,4000)") + assert_equal(first_shape(lib.layout.begin_shapes(pcell_var_id, lib.layout.layer(1, 0))).to_s, "box (-2000,-4000;2000,4000)") tl._destroy @@ -660,7 +670,7 @@ class DBPCell_TestClass < TestBase param = { "w" => 3.0, "h" => 7.0, "l" => RBA::LayerInfo::new(2, 0) } pcell_var_id = lib.layout.add_pcell_variant(pcell_decl_id, param) - assert_equal(lib.layout.begin_shapes(pcell_var_id, lib.layout.layer(2, 0)).shape.to_s, "box (-1500,-3500;1500,3500)") + assert_equal(first_shape(lib.layout.begin_shapes(pcell_var_id, lib.layout.layer(2, 0))).to_s, "box (-1500,-3500;1500,3500)") tl._destroy @@ -676,7 +686,7 @@ class DBPCell_TestClass < TestBase param = { "w" => 3.0, "h" => 8.0, "l" => RBA::LayerInfo::new(3, 0) } pcell_var = lib.layout.create_cell("Box", param) - assert_equal(lib.layout.begin_shapes(pcell_var.cell_index, lib.layout.layer(3, 0)).shape.to_s, "box (-1500,-4000;1500,4000)") + assert_equal(first_shape(lib.layout.begin_shapes(pcell_var.cell_index, lib.layout.layer(3, 0))).to_s, "box (-1500,-4000;1500,4000)") tl._destroy @@ -693,7 +703,7 @@ class DBPCell_TestClass < TestBase param = { "w" => 4.0, "h" => 8.0, "l" => RBA::LayerInfo::new(4, 0) } cell = ly.create_cell("Box", "PCellTestLib", param) - assert_equal(ly.begin_shapes(cell, ly.layer(4, 0)).shape.to_s, "box (-200,-400;200,400)") + assert_equal(first_shape(ly.begin_shapes(cell, ly.layer(4, 0))).to_s, "box (-200,-400;200,400)") tl._destroy ly._destroy @@ -715,7 +725,7 @@ class DBPCell_TestClass < TestBase cell = ly.create_cell("BOXVAR", "PCellTestLib") - assert_equal(cell.begin_shapes_rec(ly.layer(5, 0)).shape.to_s, "box (-100,-300;100,300)") + assert_equal(first_shape(cell.begin_shapes_rec(ly.layer(5, 0))).to_s, "box (-100,-300;100,300)") tl._destroy ly._destroy From 1407ae13a7935e7f3d07521031443a93cb89907d Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 26 Oct 2024 21:40:41 +0200 Subject: [PATCH 07/10] Also provide layout locking for recursive instance iterator --- src/db/db/dbRecursiveInstanceIterator.cc | 43 ++++++++---- src/db/db/dbRecursiveInstanceIterator.h | 12 ++-- src/db/db/dbRecursiveShapeIterator.cc | 16 ++--- .../dbRecursiveInstanceIteratorTests.cc | 66 +++++++++++++++++++ 4 files changed, 111 insertions(+), 26 deletions(-) diff --git a/src/db/db/dbRecursiveInstanceIterator.cc b/src/db/db/dbRecursiveInstanceIterator.cc index 2b9efb3d0..e06a5f09b 100644 --- a/src/db/db/dbRecursiveInstanceIterator.cc +++ b/src/db/db/dbRecursiveInstanceIterator.cc @@ -61,6 +61,7 @@ RecursiveInstanceIterator &RecursiveInstanceIterator::operator= (const Recursive m_box_convert = d.m_box_convert; + m_locker = d.m_locker; m_inst = d.m_inst; m_inst_array = d.m_inst_array; m_empty_cells_cache = d.m_empty_cells_cache; @@ -175,7 +176,7 @@ RecursiveInstanceIterator::set_region (const box_type ®ion) { if (m_region != region || mp_complex_region.get () != 0) { init_region (region); - m_needs_reinit = true; + reset (); } } @@ -183,7 +184,7 @@ void RecursiveInstanceIterator::set_region (const region_type ®ion) { init_region (region); - m_needs_reinit = true; + reset (); } void @@ -196,7 +197,7 @@ RecursiveInstanceIterator::confine_region (const box_type ®ion) } else { init_region (m_region & region); } - m_needs_reinit = true; + reset (); } void @@ -209,7 +210,7 @@ RecursiveInstanceIterator::confine_region (const region_type ®ion) } else { init_region (region & region_type (m_region)); } - m_needs_reinit = true; + reset (); } void @@ -218,7 +219,7 @@ RecursiveInstanceIterator::enable_all_targets () if (! m_all_targets) { m_all_targets = true; m_targets.clear (); - m_needs_reinit = true; + reset (); } } @@ -228,7 +229,7 @@ RecursiveInstanceIterator::set_targets (const std::set &tgt if (m_all_targets || m_targets != tgt) { m_targets = tgt; m_all_targets = false; - m_needs_reinit = true; + reset (); } } @@ -264,6 +265,8 @@ RecursiveInstanceIterator::validate (RecursiveInstanceReceiver *receiver) const m_needs_reinit = false; // re-initialize + m_locker = db::LayoutLocker (); + mp_cell = mp_top_cell; m_trans_stack.clear (); m_inst_iterators.clear (); @@ -310,6 +313,17 @@ RecursiveInstanceIterator::validate (RecursiveInstanceReceiver *receiver) const next_instance (receiver); } + + if (mp_layout && ! at_end ()) { + m_locker = db::LayoutLocker (const_cast (mp_layout.get ()), true); + } +} + +void +RecursiveInstanceIterator::reset () +{ + m_needs_reinit = true; + m_locker = db::LayoutLocker (); } void @@ -320,7 +334,7 @@ RecursiveInstanceIterator::reset_selection () m_start.clear (); m_stop.clear (); - m_needs_reinit = true; + reset (); } } @@ -335,7 +349,7 @@ RecursiveInstanceIterator::unselect_cells (const std::set & m_start.erase (*c); } - m_needs_reinit = true; + reset (); } } @@ -350,7 +364,7 @@ RecursiveInstanceIterator::unselect_all_cells () m_stop.insert (c->cell_index ()); } - m_needs_reinit = true; + reset (); } } @@ -365,7 +379,7 @@ RecursiveInstanceIterator::select_cells (const std::set &ce m_stop.erase (*c); } - m_needs_reinit = true; + reset (); } } @@ -380,7 +394,7 @@ RecursiveInstanceIterator::select_all_cells () m_start.insert (c->cell_index ()); } - m_needs_reinit = true; + reset (); } } @@ -441,6 +455,7 @@ void RecursiveInstanceIterator::next (RecursiveInstanceReceiver *receiver) { if (! at_end ()) { + ++m_inst_array; if (! m_inst_array.at_end ()) { new_inst_member (receiver); @@ -449,6 +464,12 @@ RecursiveInstanceIterator::next (RecursiveInstanceReceiver *receiver) new_inst (receiver); } next_instance (receiver); + + if (at_end ()) { + // Take this opportunity the release the layout lock. + // This way, the shape iterator can be held further, without blocking the layout. + m_locker = db::LayoutLocker (); + } } } diff --git a/src/db/db/dbRecursiveInstanceIterator.h b/src/db/db/dbRecursiveInstanceIterator.h index dd06c5cf5..5b746fbf3 100644 --- a/src/db/db/dbRecursiveInstanceIterator.h +++ b/src/db/db/dbRecursiveInstanceIterator.h @@ -141,7 +141,7 @@ public: { if (m_max_depth != depth) { m_max_depth = depth; - m_needs_reinit = true; + reset (); } } @@ -164,7 +164,7 @@ public: { if (m_min_depth != depth) { m_min_depth = depth; - m_needs_reinit = true; + reset (); } } @@ -262,17 +262,14 @@ public: { if (m_overlapping != f) { m_overlapping = f; - m_needs_reinit = true; + reset (); } } /** * @brief Reset the iterator */ - void reset () - { - m_needs_reinit = true; - } + void reset (); /** * @brief Gets the selected target cells @@ -552,6 +549,7 @@ private: std::unique_ptr mp_complex_region; db::box_convert m_box_convert; + mutable db::LayoutLocker m_locker; mutable inst_iterator m_inst; mutable inst_array_iterator m_inst_array; mutable instance_element_type m_combined_instance; diff --git a/src/db/db/dbRecursiveShapeIterator.cc b/src/db/db/dbRecursiveShapeIterator.cc index 27d86e2aa..d9994162f 100644 --- a/src/db/db/dbRecursiveShapeIterator.cc +++ b/src/db/db/dbRecursiveShapeIterator.cc @@ -511,7 +511,14 @@ RecursiveShapeIterator::validate (RecursiveShapeReceiver *receiver) const } } -void +void +RecursiveShapeIterator::reset () +{ + m_needs_reinit = true; + m_locker = db::LayoutLocker (); +} + +void RecursiveShapeIterator::reset_selection () { if (mp_layout) { @@ -554,13 +561,6 @@ RecursiveShapeIterator::unselect_all_cells () } } -void -RecursiveShapeIterator::reset () -{ - m_needs_reinit = true; - m_locker = db::LayoutLocker (); -} - void RecursiveShapeIterator::select_cells (const std::set &cells) { diff --git a/src/db/unit_tests/dbRecursiveInstanceIteratorTests.cc b/src/db/unit_tests/dbRecursiveInstanceIteratorTests.cc index eb06a6967..92ff90e4d 100644 --- a/src/db/unit_tests/dbRecursiveInstanceIteratorTests.cc +++ b/src/db/unit_tests/dbRecursiveInstanceIteratorTests.cc @@ -790,3 +790,69 @@ TEST(6) ); } +// layout locking +TEST(7_LayoutLocking) +{ + db::Layout layout; + + layout.insert_layer (0); + + db::Cell &c0 (layout.cell (layout.add_cell ())); + db::Cell &c1 (layout.cell (layout.add_cell ())); + + db::Box b (0, 100, 1000, 1200); + c1.shapes (0).insert (b); + + db::Trans tt; + c0.insert (db::CellInstArray (db::CellInst (c1.cell_index ()), tt)); + c0.insert (db::CellInstArray (db::CellInst (c1.cell_index ()), db::Trans (db::Vector (2000, -2000)))); + + EXPECT_EQ (layout.under_construction (), false); + + db::RecursiveInstanceIterator iter (layout, c0); + + EXPECT_EQ (layout.under_construction (), false); + + EXPECT_EQ (iter.at_end (), false); + EXPECT_EQ (layout.under_construction (), true); + + EXPECT_EQ (iter.instance ().to_string (), "cell_index=1 r0 *1 0,0"); + EXPECT_EQ (layout.under_construction (), true); + ++iter; + + EXPECT_EQ (iter.at_end (), false); + + EXPECT_EQ (iter.instance ().to_string (), "cell_index=1 r0 *1 2000,-2000"); + EXPECT_EQ (layout.under_construction (), true); + ++iter; + + EXPECT_EQ (layout.under_construction (), false); + EXPECT_EQ (iter.at_end (), true); + + // reset will restart + iter.reset (); + + EXPECT_EQ (layout.under_construction (), false); + + EXPECT_EQ (iter.at_end (), false); + EXPECT_EQ (layout.under_construction (), true); + + // a copy will hold the lock + iter.reset (); + + EXPECT_EQ (layout.under_construction (), false); + EXPECT_EQ (iter.at_end (), false); + + EXPECT_EQ (layout.under_construction (), true); + db::RecursiveInstanceIterator iter_copy = iter; + + while (! iter.at_end ()) { + ++iter; + } + + EXPECT_EQ (layout.under_construction (), true); + iter_copy = db::RecursiveInstanceIterator (); + + EXPECT_EQ (layout.under_construction (), false); +} + From 946729f14d00b62b7673b015aad596526b12f765 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 26 Oct 2024 22:46:09 +0200 Subject: [PATCH 08/10] Adding forced update to Layout writer to ensure proper files. A warning is given. --- src/db/db/dbWriter.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/db/db/dbWriter.cc b/src/db/db/dbWriter.cc index 94e6c6473..9738835f0 100644 --- a/src/db/db/dbWriter.cc +++ b/src/db/db/dbWriter.cc @@ -58,6 +58,11 @@ Writer::write (db::Layout &layout, tl::OutputStream &stream) { tl::SelfTimer timer (tl::verbosity () >= 21, tl::to_string (tr ("Writing file: ")) + stream.path ()); + if (layout.under_construction ()) { + tl::warn << tl::to_string (tr ("Cannot properly write a layout that is under construction - forcing update.")); + layout.force_update (); + } + tl_assert (mp_writer != 0); mp_writer->write (layout, stream, m_options); } From cdd705886620ea135498cc0078e5d33ecce09234 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 27 Oct 2024 23:42:16 +0100 Subject: [PATCH 09/10] Mitigating the effect of a locked layout: plain iteration of instances now is possible also with a locked layout. Before, the instances were correctly iterated only if the layout could be sorted. --- src/db/db/dbInstances.cc | 97 ++++++++++++++++++++++++++---- src/db/db/dbInstances.h | 70 +++++++++++++++++---- src/db/unit_tests/dbLayoutTests.cc | 63 +++++++++++++++++++ 3 files changed, 207 insertions(+), 23 deletions(-) diff --git a/src/db/db/dbInstances.cc b/src/db/db/dbInstances.cc index 4f1a8ca54..12804e249 100644 --- a/src/db/db/dbInstances.cc +++ b/src/db/db/dbInstances.cc @@ -280,12 +280,18 @@ void instance_iterator::release_iter () { if (m_type == TInstance) { - if (m_stable) { + if (m_stable && ! m_unsorted) { if (m_with_props) { basic_iter (cell_inst_wp_array_type::tag (), InstancesEditableTag ()).~stable_iter_wp_type (); } else { basic_iter (cell_inst_array_type::tag (), InstancesEditableTag ()).~stable_iter_type (); } + } else if (m_stable) { + if (m_with_props) { + basic_unsorted_iter (cell_inst_wp_array_type::tag (), InstancesEditableTag ()).~stable_unsorted_iter_wp_type (); + } else { + basic_unsorted_iter (cell_inst_array_type::tag (), InstancesEditableTag ()).~stable_unsorted_iter_type (); + } } else { if (m_with_props) { basic_iter (cell_inst_wp_array_type::tag (), InstancesNonEditableTag ()).~iter_wp_type (); @@ -301,12 +307,18 @@ void instance_iterator::make_iter () { if (m_type == TInstance) { - if (m_stable) { + if (m_stable && ! m_unsorted) { if (m_with_props) { new (&basic_iter (cell_inst_wp_array_type::tag (), InstancesEditableTag ())) stable_iter_wp_type (); } else { new (&basic_iter (cell_inst_array_type::tag (), InstancesEditableTag ())) stable_iter_type (); } + } else if (m_stable) { + if (m_with_props) { + new (&basic_unsorted_iter (cell_inst_wp_array_type::tag (), InstancesEditableTag ())) stable_unsorted_iter_wp_type (); + } else { + new (&basic_unsorted_iter (cell_inst_array_type::tag (), InstancesEditableTag ())) stable_unsorted_iter_type (); + } } else { if (m_with_props) { new (&basic_iter (cell_inst_wp_array_type::tag (), InstancesNonEditableTag ())) iter_wp_type (); @@ -322,18 +334,24 @@ template bool instance_iterator::operator== (const instance_iterator &d) const { - if (! (m_type == d.m_type && m_stable == d.m_stable && m_with_props == d.m_with_props)) { + if (! (m_type == d.m_type && m_stable == d.m_stable && m_with_props == d.m_with_props && m_unsorted == d.m_unsorted)) { return false; } if (m_type == TNull) { return true; } else { if (m_stable) { - if (m_with_props) { + if (m_with_props && ! m_unsorted) { return (basic_iter (cell_inst_wp_array_type::tag (), InstancesEditableTag ()) == d.basic_iter (cell_inst_wp_array_type::tag (), InstancesEditableTag ())); } else { return (basic_iter (cell_inst_array_type::tag (), InstancesEditableTag ()) == d.basic_iter (cell_inst_array_type::tag (), InstancesEditableTag ())); } + } else if (m_stable) { + if (m_with_props) { + return (basic_unsorted_iter (cell_inst_wp_array_type::tag (), InstancesEditableTag ()) == d.basic_unsorted_iter (cell_inst_wp_array_type::tag (), InstancesEditableTag ())); + } else { + return (basic_unsorted_iter (cell_inst_array_type::tag (), InstancesEditableTag ()) == d.basic_unsorted_iter (cell_inst_array_type::tag (), InstancesEditableTag ())); + } } else { if (m_with_props) { return (basic_iter (cell_inst_wp_array_type::tag (), InstancesNonEditableTag ()) == d.basic_iter (cell_inst_wp_array_type::tag (), InstancesNonEditableTag ())); @@ -355,16 +373,23 @@ instance_iterator::operator= (const instance_iterator &iter) m_type = iter.m_type; m_stable = iter.m_stable; m_with_props = iter.m_with_props; + m_unsorted = iter.m_unsorted; m_traits = iter.m_traits; if (m_type == TInstance) { - if (m_stable) { + if (m_stable && ! m_unsorted) { if (m_with_props) { new (&basic_iter (cell_inst_wp_array_type::tag (), InstancesEditableTag ())) stable_iter_wp_type (iter.basic_iter (cell_inst_wp_array_type::tag (), InstancesEditableTag ())); } else { new (&basic_iter (cell_inst_array_type::tag (), InstancesEditableTag ())) stable_iter_type (iter.basic_iter (cell_inst_array_type::tag (), InstancesEditableTag ())); } + } else if (m_stable) { + if (m_with_props) { + new (&basic_unsorted_iter (cell_inst_wp_array_type::tag (), InstancesEditableTag ())) stable_unsorted_iter_wp_type (iter.basic_unsorted_iter (cell_inst_wp_array_type::tag (), InstancesEditableTag ())); + } else { + new (&basic_unsorted_iter (cell_inst_array_type::tag (), InstancesEditableTag ())) stable_unsorted_iter_type (iter.basic_unsorted_iter (cell_inst_array_type::tag (), InstancesEditableTag ())); + } } else { if (m_with_props) { new (&basic_iter (cell_inst_wp_array_type::tag (), InstancesNonEditableTag ())) iter_wp_type (iter.basic_iter (cell_inst_wp_array_type::tag (), InstancesNonEditableTag ())); @@ -387,12 +412,18 @@ db::Box instance_iterator::quad_box () const { if (m_type == TInstance) { - if (m_stable) { + if (m_stable && ! m_unsorted) { if (m_with_props) { return m_traits.quad_box (basic_iter (cell_inst_wp_array_type::tag (), InstancesEditableTag ())); } else { return m_traits.quad_box (basic_iter (cell_inst_array_type::tag (), InstancesEditableTag ())); } + } else if (m_stable) { + if (m_with_props) { + return m_traits.quad_box (basic_unsorted_iter (cell_inst_wp_array_type::tag (), InstancesEditableTag ())); + } else { + return m_traits.quad_box (basic_unsorted_iter (cell_inst_array_type::tag (), InstancesEditableTag ())); + } } else { if (m_with_props) { return m_traits.quad_box (basic_iter (cell_inst_wp_array_type::tag (), InstancesNonEditableTag ())); @@ -409,12 +440,18 @@ size_t instance_iterator::quad_id () const { if (m_type == TInstance) { - if (m_stable) { + if (m_stable && ! m_unsorted) { if (m_with_props) { return m_traits.quad_id (basic_iter (cell_inst_wp_array_type::tag (), InstancesEditableTag ())); } else { return m_traits.quad_id (basic_iter (cell_inst_array_type::tag (), InstancesEditableTag ())); } + } else if (m_stable) { + if (m_with_props) { + return m_traits.quad_id (basic_unsorted_iter (cell_inst_wp_array_type::tag (), InstancesEditableTag ())); + } else { + return m_traits.quad_id (basic_unsorted_iter (cell_inst_array_type::tag (), InstancesEditableTag ())); + } } else { if (m_with_props) { return m_traits.quad_id (basic_iter (cell_inst_wp_array_type::tag (), InstancesNonEditableTag ())); @@ -431,12 +468,18 @@ void instance_iterator::skip_quad () { if (m_type == TInstance) { - if (m_stable) { + if (m_stable && ! m_unsorted) { if (m_with_props) { m_traits.skip_quad (basic_iter (cell_inst_wp_array_type::tag (), InstancesEditableTag ())); } else { m_traits.skip_quad (basic_iter (cell_inst_array_type::tag (), InstancesEditableTag ())); } + } else if (m_stable) { + if (m_with_props) { + m_traits.skip_quad (basic_unsorted_iter (cell_inst_wp_array_type::tag (), InstancesEditableTag ())); + } else { + m_traits.skip_quad (basic_unsorted_iter (cell_inst_array_type::tag (), InstancesEditableTag ())); + } } else { if (m_with_props) { m_traits.skip_quad (basic_iter (cell_inst_wp_array_type::tag (), InstancesNonEditableTag ())); @@ -454,12 +497,18 @@ instance_iterator & instance_iterator::operator++() { if (m_type == TInstance) { - if (m_stable) { + if (m_stable && ! m_unsorted) { if (m_with_props) { ++basic_iter (cell_inst_wp_array_type::tag (), InstancesEditableTag ()); } else { ++basic_iter (cell_inst_array_type::tag (), InstancesEditableTag ()); } + } else if (m_stable) { + if (m_with_props) { + ++basic_unsorted_iter (cell_inst_wp_array_type::tag (), InstancesEditableTag ()); + } else { + ++basic_unsorted_iter (cell_inst_array_type::tag (), InstancesEditableTag ()); + } } else { if (m_with_props) { ++basic_iter (cell_inst_wp_array_type::tag (), InstancesNonEditableTag ()); @@ -478,7 +527,7 @@ void instance_iterator::make_next () { while (true) { - if (m_stable) { + if (m_stable && ! m_unsorted) { if (m_with_props) { if (! basic_iter (cell_inst_wp_array_type::tag (), InstancesEditableTag ()).at_end ()) { return; @@ -488,6 +537,16 @@ instance_iterator::make_next () return; } } + } else if (m_stable) { + if (m_with_props) { + if (! basic_unsorted_iter (cell_inst_wp_array_type::tag (), InstancesEditableTag ()).at_end ()) { + return; + } + } else { + if (! basic_unsorted_iter (cell_inst_array_type::tag (), InstancesEditableTag ()).at_end ()) { + return; + } + } } else { if (m_with_props) { if (! basic_iter (cell_inst_wp_array_type::tag (), InstancesNonEditableTag ()).at_end ()) { @@ -514,12 +573,18 @@ void instance_iterator::update_ref () { if (m_type == TInstance) { - if (m_stable) { + if (m_stable && ! m_unsorted) { if (m_with_props) { m_ref = m_traits.instance_from_stable_iter (basic_iter (cell_inst_wp_array_type::tag (), InstancesEditableTag ())); } else { m_ref = m_traits.instance_from_stable_iter (basic_iter (cell_inst_array_type::tag (), InstancesEditableTag ())); } + } else if (m_stable) { + if (m_with_props) { + m_ref = m_traits.instance_from_stable_iter (basic_unsorted_iter (cell_inst_wp_array_type::tag (), InstancesEditableTag ())); + } else { + m_ref = m_traits.instance_from_stable_iter (basic_unsorted_iter (cell_inst_array_type::tag (), InstancesEditableTag ())); + } } else { if (m_with_props) { m_ref = value_type (m_traits.instances (), *basic_iter (cell_inst_wp_array_type::tag (), InstancesNonEditableTag ())); @@ -551,7 +616,7 @@ void NormalInstanceIteratorTraits::init (instance_iterator *iter) const { tl_assert (mp_insts != 0); - if (iter->m_stable) { + if (iter->m_stable && ! iter->m_unsorted) { if (iter->m_with_props) { cell_inst_wp_array_type::tag tag = cell_inst_wp_array_type::tag (); iter->basic_iter (tag, InstancesEditableTag ()) = mp_insts->inst_tree (tag, InstancesEditableTag ()).begin_flat (); @@ -559,6 +624,14 @@ NormalInstanceIteratorTraits::init (instance_iteratorbasic_iter (tag, InstancesEditableTag ()) = mp_insts->inst_tree (tag, InstancesEditableTag ()).begin_flat (); } + } else if (iter->m_stable) { + if (iter->m_with_props) { + cell_inst_wp_array_type::tag tag = cell_inst_wp_array_type::tag (); + iter->basic_unsorted_iter (tag, InstancesEditableTag ()) = stable_unsorted_iter_wp_type (mp_insts->inst_tree (tag, InstancesEditableTag ()).begin (), mp_insts->inst_tree (tag, InstancesEditableTag ()).end ()); + } else { + cell_inst_array_type::tag tag = cell_inst_array_type::tag (); + iter->basic_unsorted_iter (tag, InstancesEditableTag ()) = stable_unsorted_iter_type (mp_insts->inst_tree (tag, InstancesEditableTag ()).begin (), mp_insts->inst_tree (tag, InstancesEditableTag ()).end ()); + } } else { if (iter->m_with_props) { cell_inst_wp_array_type::tag tag = cell_inst_wp_array_type::tag (); diff --git a/src/db/db/dbInstances.h b/src/db/db/dbInstances.h index 975904e28..a9f11a17d 100644 --- a/src/db/db/dbInstances.h +++ b/src/db/db/dbInstances.h @@ -527,7 +527,9 @@ public: typedef typename IterTraits::iter_wp_type iter_wp_type; typedef typename IterTraits::stable_iter_type stable_iter_type; typedef typename IterTraits::stable_iter_wp_type stable_iter_wp_type; - typedef const value_type *pointer; + typedef typename IterTraits::stable_unsorted_iter_type stable_unsorted_iter_type; + typedef typename IterTraits::stable_unsorted_iter_wp_type stable_unsorted_iter_wp_type; + typedef const value_type *pointer; typedef value_type reference; // operator* returns a value typedef std::forward_iterator_tag iterator_category; typedef void difference_type; @@ -541,7 +543,7 @@ public: * @brief Default ctor */ instance_iterator () - : m_with_props (false), m_stable (false), m_type (TNull), m_traits () + : m_type (TNull), m_with_props (false), m_stable (false), m_unsorted (false), m_traits () { } /** @@ -556,7 +558,7 @@ public: * @brief Constructor */ instance_iterator (const IterTraits &traits) - : m_with_props (false), m_stable (traits.instances ()->is_editable ()), m_type (TInstance), m_traits (traits) + : m_type (TInstance), m_with_props (false), m_stable (traits.instances ()->is_editable ()), m_unsorted (traits.instances ()->instance_tree_needs_sort ()), m_traits (traits) { make_iter (); make_next (); @@ -567,7 +569,7 @@ public: * @brief Copy constructor */ instance_iterator (const instance_iterator &iter) - : m_with_props (false), m_stable (false), m_type (TNull), m_traits () + : m_type (TNull), m_with_props (false), m_stable (false), m_unsorted (false), m_traits () { operator= (iter); } @@ -661,7 +663,7 @@ public: */ stable_iter_type &basic_iter (cell_inst_array_type::tag, InstancesEditableTag) { - tl_assert (m_type == TInstance && m_stable == true && m_with_props == false); + tl_assert (m_type == TInstance && m_stable == true && m_with_props == false && m_unsorted == false); return *((stable_iter_type *) m_generic.stable_iter); } @@ -670,10 +672,28 @@ public: */ const stable_iter_type &basic_iter (cell_inst_array_type::tag, InstancesEditableTag) const { - tl_assert (m_type == TInstance && m_stable == true && m_with_props == false); + tl_assert (m_type == TInstance && m_stable == true && m_with_props == false && m_unsorted == false); return *((stable_iter_type *) m_generic.stable_iter); } + /** + * @brief Gets the basic iterator in the editable case without properties and in the unsorted case + */ + stable_unsorted_iter_type &basic_unsorted_iter (cell_inst_array_type::tag, InstancesEditableTag) + { + tl_assert (m_type == TInstance && m_stable == true && m_with_props == false && m_unsorted == true); + return *((stable_unsorted_iter_type *) m_generic.stable_unsorted_iter); + } + + /** + * @brief Gets the basic iterator in the editable case without properties and in the unsorted case (const version) + */ + const stable_unsorted_iter_type &basic_unsorted_iter (cell_inst_array_type::tag, InstancesEditableTag) const + { + tl_assert (m_type == TInstance && m_stable == true && m_with_props == false && m_unsorted == true); + return *((stable_unsorted_iter_type *) m_generic.stable_unsorted_iter); + } + /** * @brief Gets the basic iterator in the non-editable case with properties */ @@ -697,7 +717,7 @@ public: */ stable_iter_wp_type &basic_iter (cell_inst_wp_array_type::tag, InstancesEditableTag) { - tl_assert (m_type == TInstance && m_stable == true && m_with_props == true); + tl_assert (m_type == TInstance && m_stable == true && m_with_props == true && m_unsorted == false); return *((stable_iter_wp_type *) m_generic.pstable_iter); } @@ -706,10 +726,28 @@ public: */ const stable_iter_wp_type &basic_iter (cell_inst_wp_array_type::tag, InstancesEditableTag) const { - tl_assert (m_type == TInstance && m_stable == true && m_with_props == true); + tl_assert (m_type == TInstance && m_stable == true && m_with_props == true && m_unsorted == false); return *((stable_iter_wp_type *) m_generic.pstable_iter); } + /** + * @brief Gets the basic iterator in the editable case with properties and in the unsorted case + */ + stable_unsorted_iter_wp_type &basic_unsorted_iter (cell_inst_wp_array_type::tag, InstancesEditableTag) + { + tl_assert (m_type == TInstance && m_stable == true && m_with_props == true && m_unsorted == true); + return *((stable_unsorted_iter_wp_type *) m_generic.pstable_unsorted_iter); + } + + /** + * @brief Gets the basic iterator in the editable case with properties and in the unsorted case (const version) + */ + const stable_unsorted_iter_wp_type &basic_unsorted_iter (cell_inst_wp_array_type::tag, InstancesEditableTag) const + { + tl_assert (m_type == TInstance && m_stable == true && m_with_props == true && m_unsorted == true); + return *((stable_unsorted_iter_wp_type *) m_generic.pstable_unsorted_iter); + } + private: friend struct NormalInstanceIteratorTraits; friend struct OverlappingInstanceIteratorTraits; @@ -720,11 +758,14 @@ private: char piter[sizeof (iter_wp_type)]; char stable_iter[sizeof (stable_iter_type)]; char pstable_iter[sizeof (stable_iter_wp_type)]; + char stable_unsorted_iter[sizeof (stable_unsorted_iter_type)]; + char pstable_unsorted_iter[sizeof (stable_unsorted_iter_wp_type)]; } m_generic; - bool m_with_props : 8; - bool m_stable : 8; object_type m_type : 16; + bool m_with_props : 1; + bool m_stable : 1; + bool m_unsorted : 1; value_type m_ref; IterTraits m_traits; @@ -759,6 +800,8 @@ struct DB_PUBLIC NormalInstanceIteratorTraits typedef tl::iterator_pair iter_wp_type; typedef stable_cell_inst_tree_type::flat_iterator stable_iter_type; typedef stable_cell_inst_wp_tree_type::flat_iterator stable_iter_wp_type; + typedef tl::iterator_pair stable_unsorted_iter_type; + typedef tl::iterator_pair stable_unsorted_iter_wp_type; NormalInstanceIteratorTraits (); NormalInstanceIteratorTraits (const instances_type *insts); @@ -804,6 +847,8 @@ struct DB_PUBLIC TouchingInstanceIteratorTraits typedef cell_inst_wp_tree_type::touching_iterator iter_wp_type; typedef stable_cell_inst_tree_type::touching_iterator stable_iter_type; typedef stable_cell_inst_wp_tree_type::touching_iterator stable_iter_wp_type; + typedef stable_iter_type stable_unsorted_iter_type; + typedef stable_iter_wp_type stable_unsorted_iter_wp_type; TouchingInstanceIteratorTraits (); TouchingInstanceIteratorTraits (const instances_type *insts, const box_type &box, const layout_type *layout); @@ -855,6 +900,8 @@ struct DB_PUBLIC OverlappingInstanceIteratorTraits typedef cell_inst_wp_tree_type::overlapping_iterator iter_wp_type; typedef stable_cell_inst_tree_type::overlapping_iterator stable_iter_type; typedef stable_cell_inst_wp_tree_type::overlapping_iterator stable_iter_wp_type; + typedef stable_iter_type stable_unsorted_iter_type; + typedef stable_iter_wp_type stable_unsorted_iter_wp_type; OverlappingInstanceIteratorTraits (); OverlappingInstanceIteratorTraits (const instances_type *insts, const box_type &box, const layout_type *layout); @@ -1741,6 +1788,7 @@ private: friend struct NormalInstanceIteratorTraits; friend struct TouchingInstanceIteratorTraits; friend struct OverlappingInstanceIteratorTraits; + template friend class instance_iterator; template friend class InstOp; union { @@ -1773,7 +1821,7 @@ private: } /** - * @brief Sets a flag indicating that the instance tree needs sorting + * @brief Gets a flag indicating that the instance tree needs sorting */ bool instance_tree_needs_sort () const { diff --git a/src/db/unit_tests/dbLayoutTests.cc b/src/db/unit_tests/dbLayoutTests.cc index 6bd145478..02d26a22d 100644 --- a/src/db/unit_tests/dbLayoutTests.cc +++ b/src/db/unit_tests/dbLayoutTests.cc @@ -28,6 +28,7 @@ #include "dbTextWriter.h" #include "dbCellMapping.h" #include "dbInstElement.h" +#include "dbWriter.h" #include "tlString.h" #include "tlUnitTest.h" @@ -872,6 +873,68 @@ TEST(11_FindPath) EXPECT_EQ (d, "cell_index=1 r90 *1 0,0;cell_index=2 r0 *1 100,200"); } +// Shapes can be flat-iterated even in locked layout +TEST(12_ShapesInLockedLayout) +{ + db::Layout l; + db::Cell &top = l.cell (l.add_cell ("TOP")); + unsigned int l1 = l.insert_layer (db::LayerProperties (1, 0)); + + { + db::LayoutLocker locker (&l); + EXPECT_EQ (l.under_construction (), true); + + top.shapes (l1).insert (db::Box (1, 2, 3, 4)); + top.shapes (l1).insert (db::Box (10, 20, 30, 40)); + top.shapes (l1).insert (db::Box (100, 200, 300, 400)); + + double a = 0; + for (auto s = top.shapes (l1).begin (db::ShapeIterator::All); ! s.at_end (); ++s) { + a += s->area (); + } + EXPECT_EQ (a, 40404.0); + } + + EXPECT_EQ (l.under_construction (), false); + + double a = 0; + for (auto s = top.shapes (l1).begin (db::ShapeIterator::All); ! s.at_end (); ++s) { + a += s->area (); + } + EXPECT_EQ (a, 40404.0); +} + +// Instances can be flat-iterated even in locked layout +TEST(13_InstancesInLockedLayout) +{ + db::Layout l; + db::Cell &top = l.cell (l.add_cell ("TOP")); + db::Cell &c = l.cell (l.add_cell ("C")); + + { + db::LayoutLocker locker (&l); + EXPECT_EQ (l.under_construction (), true); + + top.insert (db::CellInstArray (c.cell_index (), db::Trans (db::Vector (10, 20)))); + top.insert (db::CellInstArray (c.cell_index (), db::Trans (db::Vector (100, 200)))); + top.insert (db::CellInstArray (c.cell_index (), db::Trans (db::Vector (1, 2)))); + + db::Vector a; + for (auto i = top.begin (); ! i.at_end (); ++i) { + a += i->cell_inst ().front ().disp (); + } + EXPECT_EQ (a.to_string (), "111,222"); + } + + EXPECT_EQ (l.under_construction (), false); + + db::Vector a; + for (auto i = top.begin (); ! i.at_end (); ++i) { + a += i->cell_inst ().front ().disp (); + } + EXPECT_EQ (a.to_string (), "111,222"); +} + // issue #1860 TEST(100_UndoOfDeleteLayer) { From ac1126f5c49c0866037aa643481da5357e2885c3 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 29 Oct 2024 21:01:31 +0100 Subject: [PATCH 10/10] update documentation --- src/db/db/gsiDeclDbRecursiveInstanceIterator.cc | 5 +++++ src/db/db/gsiDeclDbRecursiveShapeIterator.cc | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/db/db/gsiDeclDbRecursiveInstanceIterator.cc b/src/db/db/gsiDeclDbRecursiveInstanceIterator.cc index b6aca2bd4..85b91379c 100644 --- a/src/db/db/gsiDeclDbRecursiveInstanceIterator.cc +++ b/src/db/db/gsiDeclDbRecursiveInstanceIterator.cc @@ -603,6 +603,11 @@ Class decl_RecursiveInstanceIterator ("db", "Recu "and the target cell of the current instance.\n" "\n" "The RecursiveInstanceIterator class has been introduced in version 0.27.\n" + "Starting with version 0.29.9, the recursive instance iterator will lock the layout it acts on while in iterating mode. " + "While the iterator is active, the Layout object is maintained in 'under construction mode' (see \\Layout#under_construction). " + "This is to prevent layout modifications to interfere with the iterator's operation. Specifically when coding in Ruby, " + "pending iterators may block the Layout until the garbage collector cleans up these objects. To avoid this, call \\_destroy " + "on the iterator when you no longer need it. The Layout is automatically unlocked when the iterator reaches the end." ); } diff --git a/src/db/db/gsiDeclDbRecursiveShapeIterator.cc b/src/db/db/gsiDeclDbRecursiveShapeIterator.cc index 50f840eee..f9ac6ea71 100644 --- a/src/db/db/gsiDeclDbRecursiveShapeIterator.cc +++ b/src/db/db/gsiDeclDbRecursiveShapeIterator.cc @@ -804,6 +804,11 @@ Class decl_RecursiveShapeIterator ("db", "RecursiveS "all cells not starting with one of that letters.\n" "\n" "The RecursiveShapeIterator class has been introduced in version 0.18 and has been extended substantially in 0.23.\n" + "Starting with version 0.29.9, the recursive shape iterator will lock the layout it acts on while in iterating mode. " + "While the iterator is active, the Layout object is maintained in 'under construction mode' (see \\Layout#under_construction). " + "This is to prevent layout modifications to interfere with the iterator's operation. Specifically when coding in Ruby, " + "pending iterators may block the Layout until the garbage collector cleans up these objects. To avoid this, call \\_destroy " + "on the iterator when you no longer need it. The Layout is automatically unlocked when the iterator reaches the end." ); }