diff --git a/Lib/test/test_regions/test_enum.py b/Lib/test/test_regions/test_enum.py new file mode 100644 index 00000000000000..df1871fbc6965b --- /dev/null +++ b/Lib/test/test_regions/test_enum.py @@ -0,0 +1,525 @@ +import unittest +from regions import Region, is_local +from immutable import freeze + + +class TestRegionEnumerateBasic(unittest.TestCase): + """Tests for basic enumerate construction and LRC behavior with regions.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_enumerate_from_region_iterator_increases_lrc(self): + """ + Creating an enumerate from a region's iterator should increase + the LRC by 1, since enumerate holds a reference to the iterator. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.arr = [r.a, r.b] + r.it_arr = iter(r.arr) + base_lrc = r._lrc + + obj = enumerate(r.it_arr) + self.assertEqual(r._lrc, base_lrc + 1) + + def test_enumerate_set_to_none_decreases_lrc(self): + """ + Setting the enumerate object to None should release the borrowed + reference to the iterator, bringing LRC back to its pre-enumerate level. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.arr = [r.a, r.b] + r.it_arr = iter(r.arr) + base_lrc = r._lrc + + obj = enumerate(r.it_arr) + self.assertEqual(r._lrc, base_lrc + 1) + + obj = None + self.assertEqual(r._lrc, base_lrc) + + def test_enumerate_from_local_iterator_does_not_change_lrc(self): + """ + Creating an enumerate from a local (non-region) iterator should + not affect the LRC at all. + """ + r = Region() + base_lrc = r._lrc + + local_list = [self.A(), self.A()] + obj = enumerate(local_list) + self.assertEqual(r._lrc, base_lrc) + + +class TestRegionEnumerateNext(unittest.TestCase): + """Tests for next() calls on enumerate objects and their effect on LRC.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_next_on_enumerate_increases_lrc(self): + """ + Calling next() on an enumerate over a region iterator should + yield a (index, element) tuple. The element is a borrowed reference, + so LRC should increase by 1. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.arr = [r.a, r.b] + r.it_arr = iter(r.arr) + base_lrc = r._lrc + obj = enumerate(r.it_arr) + self.assertEqual(r._lrc, base_lrc + 1) # Because enum object points to r.it_arr in the region + + re1 = next(obj) + self.assertEqual(r._lrc, base_lrc + 2) # Now, re1 points to the first element of r.it_arr, which is r.a, so LRC increases by 1 + + def test_next_on_enumerate_increases_lrc_each_call(self): + """ + Each successive call to next() should increase the LRC by 1, + as each yields a new borrowed element reference. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.d = self.A() + r.arr = [r.a, r.b, r.c, r.d] + r.it_arr = iter(r.arr) + obj = enumerate(r.it_arr) + base_lrc = r._lrc + + re1 = next(obj) + self.assertEqual(r._lrc, base_lrc + 1) + + re2 = next(obj) + self.assertEqual(r._lrc, base_lrc + 2) + + re3 = next(obj) + self.assertEqual(r._lrc, base_lrc + 3) + + re4 = next(obj) + self.assertEqual(r._lrc, base_lrc + 4) + + # @unittest.expectedFailure + def test_next_result_moved_into_region_does_not_increase_lrc(self): + """ + Assigning the result of next() directly into a region should + transfer ownership rather than creating an external borrow, + so LRC should not increase beyond the base. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.arr = [r.a, r.b] + r.it_arr = iter(r.arr) + obj = enumerate(r.it_arr) + base_lrc = r._lrc + r.re1 = next(obj) + self.assertEqual(r._lrc, base_lrc+1) + + def test_next_result_moved_into_region_does_not_increase_lrc_2(self): + """ + Assigning the result of next() directly into a region should + transfer ownership rather than creating an external borrow, + so LRC should not increase beyond the base. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.d = self.A() + r.arr = [r.a, r.b, r.c, r.d] + r.it_arr = iter(r.arr) + obj = enumerate(r.it_arr) + base_lrc = r._lrc + r.re1 = next(obj) + self.assertEqual(r._lrc, base_lrc+1) + r.re2 = next(obj) + self.assertEqual(r._lrc, base_lrc) + + def test_next_mixed_local_and_region_assignment(self): + """ + A mix of local and region assignments from next() should + reflect only the local (external) borrows in the LRC. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.d = self.A() + r.arr = [r.a, r.b, r.c, r.d] + r.it_arr = iter(r.arr) + obj = enumerate(r.it_arr) + base_lrc = r._lrc + + re1 = next(obj) # local borrow: LRC + 1 + r.re2 = next(obj) # moved into region: LRC stays + re3 = next(obj) # local borrow: LRC + 1 + r.re4 = next(obj) # moved into region: LRC stays + self.assertEqual(r._lrc, base_lrc + 2) + + +class TestRegionEnumerateRelease(unittest.TestCase): + """Tests for releasing enumerate results and their effect on LRC.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + # @unittest.expectedFailure + def test_setting_next_result_to_none_decreases_lrc(self): + """ + Setting a local next() result to None should release the + borrowed reference and reduce LRC by 1. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.arr = [r.a, r.b] + r.it_arr = iter(r.arr) + obj = enumerate(r.it_arr) + + re1 = next(obj) + base_lrc = r._lrc + + re1 = None + self.assertEqual(r._lrc, base_lrc) + + # @unittest.expectedFailure + def test_setting_all_next_results_to_none_restores_lrc(self): + """ + Releasing all next() results should bring the LRC back + to the pre-next() level, reflecting no more external borrows. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.arr = [r.a, r.b] + r.it_arr = iter(r.arr) + obj = enumerate(r.it_arr) + base_lrc = r._lrc + + re1 = next(obj) + re2 = next(obj) + self.assertEqual(r._lrc, base_lrc + 2) + + re1 = None + self.assertEqual(r._lrc, base_lrc + 1) + + re2 = None + self.assertEqual(r._lrc, base_lrc) + + def test_enumerate_set_to_none_after_next_releases_iterator_ref(self): + """ + Releasing the enumerate object itself (not the results) should + drop LRC by 1 for the iterator reference it held. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.arr = [r.a, r.b] + r.it_arr = iter(r.arr) + + re1 = next(enumerate(r.it_arr)) + base_lrc = r._lrc + + obj = enumerate(r.it_arr) + self.assertEqual(r._lrc, base_lrc + 1) + + obj = None + self.assertEqual(r._lrc, base_lrc) + + +class TestRegionEnumerateMoveIntoRegion(unittest.TestCase): + """Tests for moving enumerate objects and results into regions.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_enumerate_moved_into_region_adjusts_lrc(self): + """ + Moving an enumerate object into a region should transfer ownership + of the iterator reference, so LRC should not increase from the + external variable, but a reference is still held by the region. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.arr = [r.a, r.b] + r.it_arr = iter(r.arr) + base_lrc = r._lrc + + obj = enumerate(r.it_arr) + self.assertEqual(r._lrc, base_lrc + 1) # obj holds external ref + + r.obj = obj + self.assertEqual(r._lrc, base_lrc+1) # obj points to the enumerate object inside the region now, so LRC should not increase further + + # @unittest.skip("GC ERROR") + def test_next_on_region_owned_enumerate_does_not_increase_lrc(self): + """ + Calling next() on an enumerate that is owned by a region (accessed + via region attribute) should not increase the LRC since the element + is moved into the region directly. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.arr = [r.a, r.b] + r.it_arr = iter(r.arr) + r.obj = enumerate(r.it_arr) + base_lrc = r._lrc + + r.re1 = next(r.obj) + self.assertEqual(r._lrc, base_lrc) + + # @unittest.skip("GC ERROR") + def test_next_on_region_owned_enumerate_local_assignment_increases_lrc(self): + """ + Calling next() on a region-owned enumerate and assigning to a + local variable should increase LRC by 1 (external borrow). + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.arr = [r.a, r.b] + r.it_arr = iter(r.arr) + r.obj = enumerate(r.it_arr) + base_lrc = r._lrc + + re1 = next(r.obj) + self.assertEqual(r._lrc, base_lrc + 1) + re1 = None + self.assertEqual(r._lrc, base_lrc) + r = None + + +class TestRegionEnumerateFullLifecycle(unittest.TestCase): + """End-to-end lifecycle tests matching the example script behavior.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_full_lifecycle_matches_example(self): + """ + Reproduces the exact sequence from the example script: + - create region with a, b + - create arr = [a, b] + - create it_arr = iter(arr) → moves into region + - obj = enumerate(it_arr) → LRC +1 + - re1 = next(obj) → LRC +1 + - r.re2 = next(obj) → LRC +0 (moved into region) + - obj = None → LRC -1 + - re1 = None → LRC -1 + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.arr = [r.a, r.b] + r.it_arr = iter(r.arr) + base_lrc = r._lrc + + obj = enumerate(r.it_arr) + self.assertEqual(r._lrc, base_lrc + 1) + + re1 = next(obj) + self.assertEqual(r._lrc, base_lrc + 2) + + r.re2 = next(obj) + self.assertEqual(r._lrc, base_lrc + 2) + + obj = None + self.assertEqual(r._lrc, base_lrc + 1) + + re1 = None + self.assertEqual(r._lrc, base_lrc) + + # @unittest.expectedFailure + def test_full_lifecycle_matches_example_2(self): + """ + Reproduces the exact sequence from the example script: + - create region with a, b + - create arr = [a, b] + - create it_arr = iter(arr) → moves into region + - obj = enumerate(it_arr) → LRC +1 + - re1 = next(obj) → LRC +1 + - r.re2 = next(obj) → LRC +0 (moved into region) + - obj = None → LRC -1 + - re1 = None → LRC -1 + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.arr = [r.a, r.b] + r.it_arr = iter(r.arr) + base_lrc = r._lrc + + obj = enumerate(r.it_arr) + self.assertEqual(r._lrc, base_lrc + 1) + + re1 = next(obj) + self.assertEqual(r._lrc, base_lrc + 2) + + r.re2 = next(obj) + self.assertEqual(r._lrc, base_lrc + 2) + + re1 = None + self.assertEqual(r._lrc, base_lrc + 1) # PROBLEM: LRC does not decrease + + obj = None + self.assertEqual(r._lrc, base_lrc) + + def test_enumerate_result_index_and_value_correct(self): + """ + Ensure the (index, value) tuples from next() contain the correct + index and the actual region element. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.arr = [r.a, r.b] + r.it_arr = iter(r.arr) + obj = enumerate(r.it_arr) + + idx0, val0 = next(obj) + idx1, val1 = next(obj) + + self.assertEqual(idx0, 0) + self.assertEqual(idx1, 1) + self.assertIs(val0, r.a) + self.assertIs(val1, r.b) + + def test_enumerate_with_start_offset(self): + """ + enumerate(iterable, start=N) should begin indices at N. + LRC behavior is unchanged — it still borrows the iterator. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.arr = [r.a, r.b] + r.it_arr = iter(r.arr) + base_lrc = r._lrc + + obj = enumerate(r.it_arr, start=5) + self.assertEqual(r._lrc, base_lrc + 1) + + idx0, val0 = next(obj) + self.assertEqual(idx0, 5) + self.assertIs(val0, r.a) + + def test_enumerate_exhausted_raises_stop_iteration(self): + """ + Calling next() past the end of the iterable should raise + StopIteration without affecting the LRC. + """ + r = Region() + r.a = self.A() + r.arr = [r.a] + r.it_arr = iter(r.arr) + obj = enumerate(r.it_arr) + + re1 = next(obj) + base_lrc = r._lrc + + with self.assertRaises(StopIteration): + next(obj) + + self.assertEqual(r._lrc, base_lrc) + + +class TestRegionEnumerateTwoRegions(unittest.TestCase): + """Tests for enumerate behavior when elements span multiple regions.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_enumerate_over_local_list_with_mixed_region_elements(self): + """ + Enumerating a local list containing elements from two different + regions should correctly borrow each element independently. + """ + r1 = Region() + r2 = Region() + r1.a = self.A() + r2.b = self.A() + + local_list = [r1.a, r2.b] + it = iter(local_list) + obj = enumerate(it) + + base_r1 = r1._lrc + base_r2 = r2._lrc + + re1 = next(obj) # borrows r1.a + self.assertEqual(r1._lrc, base_r1 + 1) + self.assertEqual(r2._lrc, base_r2) + + re2 = next(obj) # borrows r2.b + self.assertEqual(r1._lrc, base_r1 + 1) + self.assertEqual(r2._lrc, base_r2 + 1) + + def test_enumerate_over_local_list_with_mixed_region_elements_2(self): + """ + Enumerating a local list containing elements from two different + regions should correctly borrow each element independently. + """ + r1 = Region() + r2 = Region() + r1.a = self.A() + r1.b = self.A() + + local_list = [r1.a, r1.b] + it = iter(local_list) + with self.assertRaises(Exception): + r2.obj = enumerate(it) + + + def test_enumerate_results_released_independently_per_region(self): + """ + Releasing next() results from two different regions should + decrease each region's LRC independently. + """ + r1 = Region() + r2 = Region() + r1.a = self.A() + r2.b = self.A() + + local_list = [r1.a, r2.b] + it = iter(local_list) + obj = enumerate(it) + + idx0, val0 = next(obj) + idx1, val1 = next(obj) + + base_r1 = r1._lrc + base_r2 = r2._lrc + + val0 = None + self.assertEqual(r1._lrc, base_r1 - 1) + self.assertEqual(r2._lrc, base_r2) + + val1 = None + self.assertEqual(r1._lrc, base_r1 - 1) + self.assertEqual(r2._lrc, base_r2 - 1) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/Lib/test/test_regions/test_itertools.py b/Lib/test/test_regions/test_itertools.py new file mode 100644 index 00000000000000..e31b6d9f698017 --- /dev/null +++ b/Lib/test/test_regions/test_itertools.py @@ -0,0 +1,831 @@ +import unittest +from itertools import batched, pairwise +from regions import Region, is_local +from immutable import freeze, register_freezable + + +freeze(batched) +freeze(pairwise) + + +class TestRegionBatchedBasic(unittest.TestCase): + """Tests for basic batched iterator construction and LRC behavior with regions.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_initial_lrc(self): + r = Region() + self.assertEqual(r._lrc, 1) + + def test_batched_from_local_iter_does_not_change_lrc(self): + """ + Creating a batched iterator from a local iterator over a region array + should not change the LRC — the batched object itself is local and + holds no references yet. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr = [r.a, r.b, r.c] + + it = iter(r.arr) + base_lrc = r._lrc + + obj = batched(it, 2) + self.assertEqual(r._lrc, base_lrc) + self.assertTrue(is_local(obj)) + + def test_batched_moved_into_region_adjusts_lrc(self): + """ + Moving a batched iterator into a region should adjust the LRC: + the region now owns the batched object, so the external reference + is no longer borrowed. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr = [r.a, r.b, r.c] + r.it_arr = iter(r.arr) + base_lrc = r._lrc + + obj = batched(r.it_arr, 2) + self.assertEqual(r._lrc, base_lrc + 1) # obj borrows r.it_arr + + r.obj = obj + self.assertEqual(r._lrc, base_lrc+1) # obj points to the batched object. + + +class TestRegionBatchedNext(unittest.TestCase): + """Tests for next() on batched iterators and their effect on LRC.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_next_on_local_batched_increases_lrc(self): + """ + Calling next() on a local batched iterator yields a tuple of + borrowed references, increasing the LRC by the batch size. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.d = self.A() + r.e = self.A() + r.f = self.A() + r.arr = [r.a, r.b, r.c, r.d, r.e, r.f] + r.it_arr = iter(r.arr) + + obj = batched(r.it_arr, 3) + base_lrc = r._lrc + + x = next(obj) + # x is a tuple of 3 borrowed refs (a, b, c) + self.assertEqual(r._lrc, base_lrc + 3) + + def test_next_twice_on_local_batched_increases_lrc_cumulatively(self): + """ + Each call to next() borrows another batch of elements, so the LRC + should increase by batch_size for each call. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.d = self.A() + r.e = self.A() + r.f = self.A() + r.arr = [r.a, r.b, r.c, r.d, r.e, r.f] + r.it_arr = iter(r.arr) + + obj = batched(r.it_arr, 3) + base_lrc = r._lrc + + x = next(obj) + self.assertEqual(r._lrc, base_lrc + 3) + + y = next(obj) + self.assertEqual(r._lrc, base_lrc + 6) + + def test_next_result_released_decreases_lrc(self): + """ + Releasing the tuple returned by next() should release all borrowed + references it holds, bringing the LRC back down. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.d = self.A() + r.e = self.A() + r.f = self.A() + r.arr = [r.a, r.b, r.c, r.d, r.e, r.f] + r.it_arr = iter(r.arr) + + obj = batched(r.it_arr, 3) + base_lrc = r._lrc + + x = next(obj) + self.assertEqual(r._lrc, base_lrc + 3) + + y = next(obj) + self.assertEqual(r._lrc, base_lrc + 6) + + x = None + self.assertEqual(r._lrc, base_lrc + 3) + + y = None + self.assertEqual(r._lrc, base_lrc) + + def test_next_result_moved_into_region_adjusts_lrc(self): + """ + Assigning the result of next() directly into the region transfers + ownership of the tuple and its elements, so the LRC should not + increase by the full batch size for a region-owned result. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.d = self.A() + r.e = self.A() + r.f = self.A() + r.arr = [r.a, r.b, r.c, r.d, r.e, r.f] + r.it_arr = iter(r.arr) + + r.obj = batched(r.it_arr, 3) + base_lrc = r._lrc + + x = next(r.obj) # external local ref to tuple of 3 elements + self.assertEqual(r._lrc, base_lrc + 3) + + r.y = next(r.obj) # moved into region, no external borrow + self.assertEqual(r._lrc, base_lrc + 3) # only x's 3 refs still borrowed + + +class TestRegionBatchedRelease(unittest.TestCase): + """Tests for releasing batched iterators and their effect on LRC.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_batched_set_to_none_releases_lrc(self): + """ + Setting the batched iterator to None before consuming it should + release any references it holds (none, if next() was never called). + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr = [r.a, r.b, r.c] + r.it_arr = iter(r.arr) + base_lrc = r._lrc + + obj = batched(r.it_arr, 2) + obj = None + self.assertEqual(r._lrc, base_lrc) + + def test_partial_consumption_then_release(self): + """ + Releasing a partially consumed batched iterator (after one next()) + should only drop the reference to the batched object itself, not + any already-yielded tuples. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.d = self.A() + r.arr = [r.a, r.b, r.c, r.d] + r.it_arr = iter(r.arr) + + obj = batched(r.it_arr, 2) + x = next(obj) # borrows a, b + base_lrc = r._lrc + + obj = None # release the iterator; x still holds a, b + self.assertEqual(r._lrc, base_lrc-1) + + x = None # now release the tuple + self.assertEqual(r._lrc, base_lrc-1-2) # -1 for obj, -2 for a,b + + def test_region_owned_batched_set_to_none(self): + """ + Releasing a region-owned batched iterator by setting it to None + should clean up any internal state without external LRC leaks. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.d = self.A() + r.e = self.A() + r.f = self.A() + r.arr = [r.a, r.b, r.c, r.d, r.e, r.f] + r.it_arr = iter(r.arr) + + r.obj = batched(r.it_arr, 3) + x = next(r.obj) # borrows 3 elements externally + r.y = next(r.obj) # owned by region + + base_lrc = r._lrc + + x = None + self.assertEqual(r._lrc, base_lrc - 3) + + r.y = None + self.assertEqual(r._lrc, base_lrc - 3) # r.y was owned, no external borrow change + + +class TestRegionBatchedUnevenBatch(unittest.TestCase): + """Tests for batched iterators where the last batch is smaller than batch_size.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_last_batch_smaller(self): + """ + When the number of elements is not evenly divisible by batch_size, + the last batch should contain fewer elements, and the LRC increase + should reflect the actual number of elements in that batch. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.d = self.A() + r.e = self.A() + r.arr = [r.a, r.b, r.c, r.d, r.e] + r.it_arr = iter(r.arr) + + obj = batched(r.it_arr, 3) + base_lrc = r._lrc + + x = next(obj) # full batch: a, b, c + self.assertEqual(r._lrc, base_lrc + 3) + + y = next(obj) # partial batch: d, e + self.assertEqual(r._lrc, base_lrc + 3 + 2) + + x = None + self.assertEqual(r._lrc, base_lrc + 2) + + y = None + self.assertEqual(r._lrc, base_lrc) + + def test_single_element_batch(self): + """ + A batch size of 1 should yield one element at a time, increasing + the LRC by 1 per next() call. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.arr = [r.a, r.b] + r.it_arr = iter(r.arr) + + obj = batched(r.it_arr, 1) + base_lrc = r._lrc + + x = next(obj) + self.assertEqual(r._lrc, base_lrc + 1) + + y = next(obj) + self.assertEqual(r._lrc, base_lrc + 2) + + x = None + y = None + self.assertEqual(r._lrc, base_lrc) + + +class TestRegionBatchedFromLocalIterator(unittest.TestCase): + """Tests for batched over a local (non-region) iterator that yields region objects.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_batched_from_local_list_of_region_objects(self): + """ + Creating a batched iterator from a plain local list containing + region objects should borrow those objects when next() is called. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.d = self.A() + arr = [r.a, r.b, r.c, r.d] # local list, not in region + + obj = batched(iter(arr), 2) + base_lrc = r._lrc + + x = next(obj) + self.assertEqual(r._lrc, base_lrc + 2) + + y = next(obj) + self.assertEqual(r._lrc, base_lrc + 4) + + x = None + y = None + self.assertEqual(r._lrc, base_lrc) + + def test_batched_next_into_region_from_local_list(self): + """ + Assigning the result of next() into the region when iterating + a local list of region objects should transfer ownership. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.d = self.A() + arr = [r.a, r.b, r.c, r.d] + + obj = batched(iter(arr), 2) + base_lrc = r._lrc + + r.x = next(obj) # owned by region + self.assertEqual(r._lrc, base_lrc) + + y = next(obj) # external borrow + self.assertEqual(r._lrc, base_lrc + 2) + + y = None + self.assertEqual(r._lrc, base_lrc) + + +class TestRegionBatchedIsLocal(unittest.TestCase): + """Tests that batched iterators and their results have correct locality.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_local_batched_is_local(self): + """ + A batched iterator created outside of a region should be local. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.arr = [r.a, r.b] + it = iter(r.arr) + + obj = batched(it, 2) + self.assertTrue(is_local(obj)) + + def test_next_result_is_local(self): + """ + The tuple returned by next() on a local batched iterator should be local. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.arr = [r.a, r.b] + r.it_arr = iter(r.arr) + + obj = batched(r.it_arr, 2) + x = next(obj) + self.assertTrue(is_local(x)) + + def test_region_owned_batched_is_not_local(self): + """ + A batched iterator moved into a region should no longer be local. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.arr = [r.a, r.b] + r.it_arr = iter(r.arr) + + r.obj = batched(r.it_arr, 2) + self.assertFalse(is_local(r.obj)) + +class TestRegionPairwiseBasic(unittest.TestCase): + """Tests for basic pairwise iterator construction and LRC behavior.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_pairwise_from_region_iterator_does_not_change_lrc(self): + """ + Creating a pairwise iterator from a region-owned iterator should + not change the LRC — the pairwise object borrows the iterator + but holds no element references yet. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr = [r.a, r.b, r.c] + r.it_arr = iter(r.arr) + base_lrc = r._lrc + + obj = pairwise(r.it_arr) + # pairwise borrows r.it_arr externally + self.assertEqual(r._lrc, base_lrc + 1) + self.assertTrue(is_local(obj)) + + def test_pairwise_from_local_iterator_does_not_change_lrc(self): + """ + Creating a pairwise iterator from a local iterator (not region-owned) + should not change the LRC. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr = [r.a, r.b, r.c] + it = iter(r.arr) + base_lrc = r._lrc + + obj = pairwise(it) + self.assertEqual(r._lrc, base_lrc) + self.assertTrue(is_local(obj)) + + def test_pairwise_moved_into_region_adjusts_lrc(self): + """ + Moving a pairwise iterator into a region should transfer ownership, + removing the external borrow on the underlying iterator. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr = [r.a, r.b, r.c] + r.it_arr = iter(r.arr) + base_lrc = r._lrc + + obj = pairwise(r.it_arr) + self.assertEqual(r._lrc, base_lrc + 1) # pairwise points to r.it_arr + + r.obj = obj + self.assertEqual(r._lrc, base_lrc + 1) # obj points to pairwise + + +class TestRegionPairwiseNextLocal(unittest.TestCase): + """ + Tests for next() on a local pairwise iterator over a region-owned iterator. + + pairwise internally caches the right element of the last yielded pair as `old`. + On the first next(): + - yields (left, right): LRC +2 for the tuple elements, +1 for obj's internal + ref to `old` (right) = +3 total. + On subsequent next() calls (when `old` is already cached): + - the new left == previous right, already counted via `old`, + so: +2 for the new tuple, -1 for releasing old `old` ref = net +1... + but if previous tuple is still alive (sharing the right element), + the ref to `old` is not a new borrow = +2 net. + """ + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_first_next_increases_lrc_by_3(self): + """ + The first next() yields (a, b) and caches b as `old`. + LRC increases by 3: +2 for the tuple (a, b), +1 for internal `old` ref to b. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.d = self.A() + r.arr = [r.a, r.b, r.c, r.d] + r.it_arr = iter(r.arr) + + obj = pairwise(r.it_arr) + base_lrc = r._lrc + + x = next(obj) + self.assertEqual(r._lrc, base_lrc + 3) # tuple(a,b) + internal old->b + + def test_release_first_tuple_decreases_lrc_by_2(self): + """ + Releasing the first tuple (a, b) drops 2 refs. + The internal `old` ref to b is still held by pairwise, so LRC drops by 2. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.d = self.A() + r.arr = [r.a, r.b, r.c, r.d] + r.it_arr = iter(r.arr) + + obj = pairwise(r.it_arr) + base_lrc = r._lrc + + x = next(obj) + self.assertEqual(r._lrc, base_lrc + 3) + + x = None # releases (a, b) but old still holds b + self.assertEqual(r._lrc, base_lrc + 3) # The tuple that points to two elements in the region is not deallocated because obj->result still points to. + + def test_second_next_after_release_does_not_increase_lrc(self): + """ + After releasing x=(a,b) and calling next() again to get (b,c): + b is already borrowed via `old`, so only c is a new borrow. + But old is updated to c, releasing the old->b ref. + Net change from the second next(): 0 (new tuple borrows b,c: +2, + old releases b: -1, old takes c: already counted in tuple = net +2 -1 = +1, + but since we re-use old's slot for the right element: net 0 from base+1). + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.d = self.A() + r.arr = [r.a, r.b, r.c, r.d] + r.it_arr = iter(r.arr) + + obj = pairwise(r.it_arr) + base_lrc = r._lrc + + x = next(obj) # LRC +3 + x = None # LRC -0 + x = next(obj) # LRC +0 since the first next(obj) has set up the internal state of pairwise to reuse the old ref for the right element of the new tuple. + + self.assertEqual(r._lrc, base_lrc + 3) + + def test_second_next_while_first_tuple_alive(self): + """ + Calling next() a second time while first tuple x=(a,b) is still alive. + Second tuple y=(b,c): b is shared between x and old, c is new. + old moves from b to c: -1 for old->b, +1 for old->c, +1 for new c in y's tuple. + But b in y's tuple was already in old: net +2 for y (b already counted), -1 for old release of b, +1 new old->c. + Total from base_lrc (after first next at base+3): +2 for y = base+3+2 = base+5? + Actually: y=(b,c) borrows b (was in old, now also in tuple +1) and c (+1), old->c replaces old->b (net 0 for old). + So +2 for tuple y's new borrows. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.d = self.A() + r.arr = [r.a, r.b, r.c, r.d] + r.it_arr = iter(r.arr) + + obj = pairwise(r.it_arr) + base_lrc = r._lrc # Initial LRC includes the borrow ref from pairwise to r.it_arr + + x = next(obj) # (a,b), old=b → base+3 + self.assertEqual(r._lrc, base_lrc + 3) + + y = next(obj) # (b,c), old=c → +2 for (b,c) in y, old moves b→c (net 0) + self.assertEqual(r._lrc, base_lrc + 5) + + x = None # releases (a, b): -2 + self.assertEqual(r._lrc, base_lrc + 3) + + y = None # releases (b, c): -2 + self.assertEqual(r._lrc, base_lrc + 1) # only old->c remains + + obj = None # releases pairwise (drops old->c and it_arr borrow) + self.assertEqual(r._lrc, base_lrc - 1) + + def test_three_nexts_full_sequence(self): + """ + Full sequence over [a,b,c,d,e,f] with batch of pairs: + next1 → (a,b): +3 (tuple a,b + old b) + next2 → (b,c): +2 (tuple b,c; old moves b→c) + next3 → (c,d): +2 (tuple c,d; old moves c→d) + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.d = self.A() + r.e = self.A() + r.f = self.A() + r.arr = [r.a, r.b, r.c, r.d, r.e, r.f] + r.it_arr = iter(r.arr) + + obj = pairwise(r.it_arr) + base_lrc = r._lrc + + x = next(obj) + self.assertEqual(r._lrc, base_lrc + 3) + + y = next(obj) + self.assertEqual(r._lrc, base_lrc + 5) + + z = next(obj) + self.assertEqual(r._lrc, base_lrc + 7) + + x = None + self.assertEqual(r._lrc, base_lrc + 5) + + y = None + self.assertEqual(r._lrc, base_lrc + 3) + + z = None + self.assertEqual(r._lrc, base_lrc + 1) + + obj = None + self.assertEqual(r._lrc, base_lrc - 1) + + +class TestRegionPairwiseNextIntoRegion(unittest.TestCase): + """Tests for assigning next() results into the region.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_first_next_into_region_lrc(self): + """ + From commented block 3: + r.x = next(obj): LRC +1 from obj->tuple (owned by region) and +1 from old->right_element. + Since the tuple is owned by the region, its elements are not external borrows. + But old still holds an external ref to the right element. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.d = self.A() + r.arr = [r.a, r.b, r.c, r.d] + r.it_arr = iter(r.arr) + + obj = pairwise(r.it_arr) + base_lrc = r._lrc + + # obj->result points to tuple that is now owned by region. LRC +1 + # obj->old points to right element of tuple, which is still an external borrow. LRC +1 + r.x = next(obj) + self.assertEqual(r._lrc, base_lrc + 2) + + def test_second_next_local_after_first_into_region(self): + """ + From commented block 3: + After r.x = next(obj) (LRC base+1), y = next(obj): + yields (b,c), tuple borrowed externally (+2), old moves from b to c (-1+1=0 net for old). + But b was already in old (base+1 had old->b), so: + y's tuple borrows b (+1) and c (+1), old releases b (-1) and holds c (already in tuple). + Net: base+1 + 2(tuple y) - 1(old releases b) = base+2. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.d = self.A() + r.arr = [r.a, r.b, r.c, r.d] + r.it_arr = iter(r.arr) + + obj = pairwise(r.it_arr) + base_lrc = r._lrc + + r.x = next(obj) + self.assertEqual(r._lrc, base_lrc + 2) + + y = next(obj) # (b,c): +2 for tuple, -1 from derefing obj->result to tuple + self.assertEqual(r._lrc, base_lrc + 2 + 1) + + r.x = None # region-owned tuple released; no external change + self.assertEqual(r._lrc, base_lrc + 2 + 1) + + y = None # external tuple (b,c) released: -2 + self.assertEqual(r._lrc, base_lrc + 1) # old->c... wait, old still holds c + + obj = None + self.assertEqual(r._lrc, base_lrc - 1) + + +class TestRegionPairwiseRelease(unittest.TestCase): + """Tests for releasing the pairwise iterator and its effect on LRC.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_release_unconsumed_pairwise_from_region_iter(self): + """ + Releasing a pairwise iterator that was never consumed (no next() calls) + should release the borrow on the underlying region iterator. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr = [r.a, r.b, r.c] + r.it_arr = iter(r.arr) + base_lrc = r._lrc + + obj = pairwise(r.it_arr) + self.assertEqual(r._lrc, base_lrc + 1) + + obj = None + self.assertEqual(r._lrc, base_lrc) + + def test_release_pairwise_after_partial_consumption(self): + """ + From commented block 1: + obj = None after consuming 3 pairs (with x, y released and r.z owned): + releases obj->it_arr and obj->old (-2 total from obj's own refs). + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.d = self.A() + r.e = self.A() + r.f = self.A() + r.arr = [r.a, r.b, r.c, r.d, r.e, r.f] + r.it_arr = iter(r.arr) + + obj = pairwise(r.it_arr) + base_lrc = r._lrc + + x = next(obj) # (a,b) +3 + y = next(obj) # (b,c) +2 → base+5 + r.z = next(obj) # (c,d) owned → base+5+1(old->d) = base+... let's track: + # r.z owned so tuple not borrowed externally, old moves c→d: +1(old->d)-1(old->c) + # net from base+5: base+5-1(old c release)+1(old d) = base+5 + + x = None # -2 → base+3 + y = None # -2 → base+1 (old still holds d) + r.z = None # region owned, net 0 → base+1 + + lrc_before_obj_none = r._lrc + obj = None # releases old->d and obj->it_arr + self.assertEqual(r._lrc, lrc_before_obj_none - 2) + + +class TestRegionPairwiseIsLocal(unittest.TestCase): + """Tests for locality of pairwise iterators and their yielded tuples.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_local_pairwise_is_local(self): + r = Region() + r.a = self.A() + r.b = self.A() + r.arr = [r.a, r.b] + it = iter(r.arr) + + obj = pairwise(it) + self.assertTrue(is_local(obj)) + + def test_next_result_is_local(self): + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr = [r.a, r.b, r.c] + r.it_arr = iter(r.arr) + + obj = pairwise(r.it_arr) + x = next(obj) + self.assertTrue(is_local(x)) + + def test_region_owned_pairwise_is_not_local(self): + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr = [r.a, r.b, r.c] + r.it_arr = iter(r.arr) + + r.obj = pairwise(r.it_arr) + self.assertFalse(is_local(r.obj)) + + def test_region_owned_next_result_is_not_local(self): + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr = [r.a, r.b, r.c] + r.it_arr = iter(r.arr) + + obj = pairwise(r.it_arr) + r.x = next(obj) + self.assertFalse(is_local(r.x)) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/Lib/test/test_regions/test_set.py b/Lib/test/test_regions/test_set.py new file mode 100644 index 00000000000000..31251b6e8133ce --- /dev/null +++ b/Lib/test/test_regions/test_set.py @@ -0,0 +1,1595 @@ +import unittest +from regions import Region, is_local +from immutable import freeze, isfrozen + + +class TestRegionSet(unittest.TestCase): + """Tests for basic set construction and LRC behavior with regions.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_initial_lrc(self): + r = Region() + self.assertEqual(r._lrc, 1) + + def test_set_from_region_array_increases_lrc(self): + """ + Creating a set from a region array should increase the LRC + for each element borrowed from the region. + """ + r = Region() + r.word = self.A() + r.word2 = self.A() + r.arr = [r.word, r.word2] + base_lrc = r._lrc + + s = set(r.arr) + + self.assertEqual(r._lrc, base_lrc + 2) + + def test_set_to_none_decreases_lrc(self): + """ + Setting the set to None should release the borrowed references + and bring LRC back to its pre-set level. + """ + r = Region() + r.word = self.A() + r.word2 = self.A() + r.arr = [r.word, r.word2] + base_lrc = r._lrc + + s = set(r.arr) + self.assertEqual(r._lrc, base_lrc + 2) + + s = None + self.assertEqual(r._lrc, base_lrc) + + def test_set_moved_into_region_adjusts_lrc(self): + """ + Moving a set into a region should transfer ownership of its elements, + reducing the LRC by the number of elements (now owned) minus the + external reference to the set itself. + """ + r = Region() + r.word = self.A() + r.word2 = self.A() + r.word3 = self.A() + r.word4 = self.A() + r.arr = [r.word, r.word2, r.word3, r.word4] + base_lrc = r._lrc + + s = set(r.arr) + # Set borrows all 4 elements + self.assertEqual(r._lrc, base_lrc + 4) + + # Moving into region: region owns the set, elements no longer borrowed + # but `s` still holds an external ref + r.set = s + self.assertEqual(r._lrc, base_lrc + 1) + + def test_set_from_set_in_region_increases_lrc(self): + """ + Creating a new set from a set that is already inside a region + should borrow all elements, increasing the LRC accordingly. + """ + r = Region() + r.word = self.A() + r.word2 = self.A() + r.word3 = self.A() + r.word4 = self.A() + r.arr = [r.word, r.word2, r.word3, r.word4] + s = set(r.arr) + r.set = s + base_lrc = r._lrc + + s2 = set(r.set) + self.assertEqual(r._lrc, base_lrc + 4) + + s2 = None + self.assertEqual(r._lrc, base_lrc) + + def test_set_from_dict_keys_with_object_keys(self): + """ + Creating a set from a dict that has object keys inside a region + should borrow those keys and increase the LRC. + """ + r = Region() + r.word = {self.A(): "value", self.A(): "value2"} + base_lrc = r._lrc + + s = set(r.word) + self.assertEqual(r._lrc, base_lrc + 2) + + def test_set_from_dict_with_frozen_string_keys(self): + """ + Creating a set from a dict with frozen string keys should + not affect the LRC since strings are frozen/immutable. + """ + r = Region() + r.word = {"key": "value", "key2": "value2"} + base_lrc = r._lrc + + s = set(r.word) + # Frozen string keys don't bump LRC + self.assertEqual(r._lrc, base_lrc) + + def test_set_move_into_region_fails_if_element_in_another_region(self): + """ + Moving a set into a region should fail if any element belongs + to a different region, and the state should remain consistent. + """ + r = Region() + r2 = Region() + r2.word4 = self.A() + + aa = self.A() + ab = self.A() + ac = self.A() + arr = [aa, ab, ac, r2.word4] + s = set(arr) + + with self.assertRaises(Exception): + r.set = s + + # Locals should still be local after the failed move + self.assertTrue(is_local(aa)) + self.assertTrue(is_local(ab)) + self.assertTrue(is_local(ac)) + + + +class TestRegionSetDiscard(unittest.TestCase): + """Tests for set discard/pop operations and their effect on LRC.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_discard_decreases_lrc(self): + """ + Discarding an element from a set should release the subregion's parent. + """ + r1 = Region() + r2 = Region() + r3 = Region() + r4 = Region() + + s1 = set([r1, r2, r3]) + r4.s = s1 + self.assertEqual(r1.parent, r4) + + s1.discard(r1) + self.assertIsNone(r1.parent) + + s1.discard(r2) + self.assertIsNone(r2.parent) + + s1.discard(r3) + self.assertIsNone(r3.parent) + + def test_discard_nonexistent_element_does_not_change_lrc(self): + """ + Discarding an element that is not in the set should not + affect the LRC. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.arr1 = [r.a, r.b] + + s1 = set(r.arr1) + base_lrc = r._lrc + + s1.discard(r.a) # exists, LRC - 1 + self.assertEqual(r._lrc, base_lrc - 1) + + s1.discard(r.a) # already gone, should be no change + self.assertEqual(r._lrc, base_lrc - 1) + + +class TestRegionSetDifference(unittest.TestCase): + """Tests for set difference operations and their effect on LRC.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_set_difference_does_not_increase_lrc(self): + """ + Taking a set difference should produce a new local set. + The LRC of the source region should not increase since + the resulting set only contains elements not in the subtracted sets. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr1 = [r.a, r.b, r.c] + r.arr2 = [r.a] + r.arr3 = [r.b] + + original_lrc = r._lrc + s1 = set(r.arr1) + self.assertEqual(r._lrc, original_lrc + 3) + s2 = set(r.arr2) + self.assertEqual(r._lrc, original_lrc + 3 + 1) + s3 = set(r.arr3) + self.assertEqual(r._lrc, original_lrc + 3 + 1 + 1) + base_lrc = r._lrc + + s4 = s1.difference(s2, s3) + self.assertEqual(r._lrc, base_lrc + 1) # s4 borrows r.c, but not r.a or r.b + + # s4 only contains r.c, so LRC should reflect that + self.assertTrue(is_local(s4)) + + def test_set_difference_result_releases_lrc_on_none(self): + """ + Setting the result of a difference to None should release + any borrowed references it holds. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr1 = [r.a, r.b, r.c] + r.arr2 = [r.a] + r.arr3 = [r.b] + + s1 = set(r.arr1) + s2 = set(r.arr2) + s3 = set(r.arr3) + + s4 = s1.difference(s2, s3) + base_lrc = r._lrc + + s4 = None + self.assertLess(r._lrc, base_lrc) + + def test_set_difference_result_releases_lrc_on_none_2_elem(self): + """ + Setting the result of a difference to None should release + any borrowed references it holds. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr1 = [r.a, r.b, r.c] + r.arr2 = [r.a] + + s1 = set(r.arr1) + s2 = set(r.arr2) + base_lrc = r._lrc + + s3 = s1.difference(s2) + self.assertEqual(r._lrc, base_lrc + 2) + + +class TestRegionSetSymmetricDifference(unittest.TestCase): + """Tests for symmetric difference (XOR) operations on sets.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_symmetric_difference_result_is_local(self): + """ + The result of a symmetric difference of two sets borrowing + from a region should be a local set. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.f = self.A() + r.arr1 = [r.a, r.b, r.c] + r.arr2 = [r.b, r.c, r.f] + + original_lrc = r._lrc + s1 = set(r.arr1) + self.assertEqual(r._lrc, original_lrc + 3) + s2 = set(r.arr2) + self.assertEqual(r._lrc, original_lrc + 3 + 3) + base_lrc = r._lrc + + result = s1.symmetric_difference(s2) + self.assertEqual(r._lrc, base_lrc + 2) + self.assertTrue(is_local(result)) + + def test_symmetric_difference_lrc_released_on_none(self): + """ + Releasing the result of symmetric_difference should bring + the LRC back down by the number of unique elements it held. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.f = self.A() + r.arr1 = [r.a, r.b, r.c] + r.arr2 = [r.b, r.c, r.f] + + s1 = set(r.arr1) + s2 = set(r.arr2) + + result = s1.symmetric_difference(s2) + base_lrc = r._lrc + + result = None + # r.a and r.f are the unique elements, so LRC should drop by 2 + self.assertEqual(r._lrc, base_lrc - 2) + + def test_symmetric_difference_operator_matches_method(self): + """ + The `^` operator should behave identically to symmetric_difference(). + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.f = self.A() + r.arr1 = [r.a, r.b, r.c] + r.arr2 = [r.b, r.c, r.f] + + s1 = set(r.arr1) + s2 = set(r.arr2) + base_lrc = r._lrc + + result_method = s1.symmetric_difference(s2) + result_operator = s1 ^ s2 + self.assertEqual(r._lrc, base_lrc + 2 + 2) # both should borrow the same unique elements, and there are two result, +2 from result_method and +2 from result_operator + + self.assertEqual(result_method, result_operator) + +class TestRegionFrozenSet(unittest.TestCase): + """Tests for frozenset construction, ownership transfer, and copy behavior.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_frozenset_from_region_array_increases_lrc(self): + """ + Creating a frozenset from a region array should borrow + all elements and increase the LRC accordingly. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr1 = [r.a, r.b, r.c] + base_lrc = r._lrc + + s1 = frozenset(r.arr1) + self.assertEqual(r._lrc, base_lrc + 3) + + def test_frozenset_moved_into_region_adjusts_lrc(self): + """ + Moving a frozenset into a region transfers ownership of its + elements, reducing the borrowed references accordingly. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr1 = [r.a, r.b, r.c] + + s1 = frozenset(r.arr1) + r.set1 = s1 + base_lrc = r._lrc + + # s1 still holds an external ref but elements are now owned + self.assertEqual(r._lrc, base_lrc) + + def test_frozenset_copy_is_same_object(self): + """ + Copying a frozenset that is already inside a region should + return the same object (CPython optimizes frozenset copies), + not a new independent frozenset. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr1 = [r.a, r.b, r.c] + + s1 = frozenset(r.arr1) + r.set1 = s1 + + s2 = s1.copy() + self.assertIs(s2, r.set1) + + def test_frozenset_copy_does_not_change_lrc(self): + """ + Since frozenset.copy() returns the same object, the LRC + should increase by exactly 1 for the new reference. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr1 = [r.a, r.b, r.c] + + s1 = frozenset(r.arr1) + r.set1 = s1 + base_lrc = r._lrc + + s2 = s1.copy() + self.assertEqual(r._lrc, base_lrc + 1) + + s2 = None + self.assertEqual(r._lrc, base_lrc) + + def test_frozenset_from_another_frozenset(self): + """ + Creating a frozenset from another frozenset should return the same object, + and not increase the LRC since it's not borrowing new references. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr1 = [r.a, r.b, r.c] + + s1 = frozenset(r.arr1) + r.set1 = s1 + base_lrc = r._lrc + + s2 = frozenset(s1) + self.assertIs(s2, s1) + self.assertEqual(r._lrc, base_lrc + 1) # only the original reference to s1 increases LRC + + +class TestRegionSetCopy(unittest.TestCase): + """Tests for set copy behavior and its effect on LRC.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_copy_increases_lrc(self): + """ + Copying a set that borrows from a region should increase + the LRC since the copy also holds references to the same elements. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr1 = [r.a, r.b, r.c] + + s1 = set(r.arr1) + base_lrc = r._lrc + + s2 = s1.copy() + self.assertEqual(r._lrc, base_lrc + 3) + + def test_copy_released_decreases_lrc(self): + """ + Releasing the copied set should bring LRC back down + to the level before the copy was made. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr1 = [r.a, r.b, r.c] + + s1 = set(r.arr1) + base_lrc = r._lrc + + s2 = s1.copy() + self.assertEqual(r._lrc, base_lrc + 3) + + s2 = None + self.assertEqual(r._lrc, base_lrc) + + +class TestRegionSetIntersection(unittest.TestCase): + """Tests for set intersection operations and their effect on LRC.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_intersection_result_is_local(self): + """ + The result of an intersection of two sets borrowing from + a region should be a local set. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.f = self.A() + r.arr1 = [r.a, r.b, r.c] + r.arr2 = [r.b, r.c, r.f] + + s1 = set(r.arr1) + s2 = set(r.arr2) + + result = s1.intersection(s2) + self.assertTrue(is_local(result)) + + def test_intersection_lrc_reflects_common_elements(self): + """ + The intersection result only holds references to shared elements, + so the LRC increase should reflect only those elements. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.f = self.A() + r.arr1 = [r.a, r.b, r.c] # {a, b, c} + r.arr2 = [r.b, r.c, r.f] # {b, c, f} + + s1 = set(r.arr1) + s2 = set(r.arr2) + base_lrc = r._lrc + + result = s1.intersection(s2) # {b, c} + self.assertEqual(r._lrc, base_lrc + 2) + + result = None + self.assertEqual(r._lrc, base_lrc) + + def test_intersection_operator_matches_method(self): + """ + The `&` operator should behave identically to intersection(). + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.f = self.A() + r.arr1 = [r.a, r.b, r.c] + r.arr2 = [r.b, r.c, r.f] + + s1 = set(r.arr1) + s2 = set(r.arr2) + base_lrc = r._lrc + + _ = s1 & s2 + self.assertEqual(r._lrc, base_lrc + 2) + def test_intersection_multiple_sets(self): + """ + Intersection across three sets should only retain elements + common to all three. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.d = self.A() + r.e = self.A() + r.f = self.A() + r.arr1 = [r.a, r.b, r.c] + r.arr2 = [r.b, r.c, r.f] + r.arr3 = [r.b, r.d, r.e] + + s1 = set(r.arr1) + s2 = set(r.arr2) + s3 = set(r.arr3) + base_lrc = r._lrc + + result = s1.intersection(s2, s3) # only {b} + self.assertEqual(r._lrc, base_lrc + 1) + + result = None + self.assertEqual(r._lrc, base_lrc) + + def test_intersection_multiple_sets_2_1(self): + """ + Intersection across two sets should only retain elements + common to all two. The first set is in the region, and the second set + is in local. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.f = self.A() + r.arr1 = [r.a, r.b, r.c] + r.arr2 = [r.b, r.c, r.f] + + original_lrc = r._lrc + r.s1 = set(r.arr1) + self.assertEqual(r._lrc, original_lrc) + s2 = set(r.arr2) + self.assertEqual(r._lrc, original_lrc + 3) + base_lrc = r._lrc + + result = r.s1.intersection(s2) + self.assertEqual(r._lrc, base_lrc + 2) + + def test_intersection_multiple_sets_2_2(self): + """ + Intersection across three sets should only retain elements + common to all three. Some sets are now in the region, and some are in local. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.d = self.A() + r.e = self.A() + r.f = self.A() + r.arr1 = [r.a, r.b, r.c] + r.arr2 = [r.b, r.c, r.f] + r.arr3 = [r.b, r.e, r.f] + + original_lrc = r._lrc + r.s1 = set(r.arr1) + self.assertEqual(r._lrc, original_lrc) + s2 = set(r.arr2) + self.assertEqual(r._lrc, original_lrc + 3) + r.s3 = set(r.arr3) + self.assertEqual(r._lrc, original_lrc + 3) + base_lrc = r._lrc + + result = r.s1.intersection(s2, r.s3) + self.assertEqual(r._lrc, base_lrc + 1) + + +class TestRegionSetIntersectionUpdate(unittest.TestCase): + """Tests for in-place intersection (intersection_update / &=) and LRC.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_intersection_update_removes_non_common_refs(self): + """ + intersection_update should release references to elements removed + from s1 and retain only those in the intersection. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.f = self.A() + r.arr1 = [r.a, r.b, r.c] # {a, b, c} + r.arr2 = [r.b, r.c, r.f] # {b, c, f} + + s1 = set(r.arr1) + s2 = set(r.arr2) + base_lrc = r._lrc + + s1.intersection_update(s2) # s1 becomes {b, c}, drops ref to a + # s1 now holds 2 refs (b, c), s2 holds 3 refs (b, c, f) + self.assertEqual(r._lrc, base_lrc - 3 + 2) # -3 for dropping a b c, +2 for retaining b and c + + def test_intersection_update_removes_non_common_refs_2(self): + """ + intersection_update should release references to elements removed + from s1 and retain only those in the intersection. Using &= instead. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.f = self.A() + r.arr1 = [r.a, r.b, r.c] # {a, b, c} + r.arr2 = [r.b, r.c, r.f] # {b, c, f} + + s1 = set(r.arr1) + s2 = set(r.arr2) + base_lrc = r._lrc + + s1 &= s2 # s1 becomes {b, c}, drops ref to a + # s1 now holds 2 refs (b, c), s2 holds 3 refs (b, c, f) + self.assertEqual(r._lrc, base_lrc - 3 + 2) # -3 for dropping a b c, +2 for retaining b and c + + def test_intersection_update_operator_matches_method(self): + """ + The `&=` operator should behave identically to intersection_update(). + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.f = self.A() + r.arr1 = [r.a, r.b, r.c] + r.arr2 = [r.b, r.c, r.f] + + s1_method = set(r.arr1) + s1_operator = set(r.arr1) + s2 = set(r.arr2) + + s1_method.intersection_update(s2) + s1_operator &= s2 + self.assertEqual(s1_method, s1_operator) + + def test_intersection_update_swap_bodies_different_region(self): + """ + the first set is in the local, but the second set is in the region. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.f = self.A() + original_lrc = r._lrc + arr1 = [r.a, r.b, r.c] + r.arr2 = [r.b, r.c, r.f] + self.assertEqual(r._lrc, original_lrc + 3) + + s1 = set(arr1) + r.s2 = set(r.arr2) + self.assertEqual(r._lrc, original_lrc + 3 + 3) + base_lrc = r._lrc + + s1.intersection_update(r.s2) + self.assertEqual(r._lrc, base_lrc - 3 + 2) # -3 for dropping a b c, +2 for retaining b and c + + def test_intersection_update_swap_bodies_different_region_2_intersection_update(self): + """ + the first set is in the region, but the second set is in the local. Using intersection_update(). + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.f = self.A() + original_lrc = r._lrc + r.arr1 = [r.a, r.b, r.c] + arr2 = [r.b, r.c, r.f] + self.assertEqual(r._lrc, original_lrc + 3) + + r.s1 = set(r.arr1) + s2 = set(arr2) + self.assertEqual(r._lrc, original_lrc + 3 + 3) + base_lrc = r._lrc + + r.s1.intersection_update(s2) + self.assertEqual(r._lrc, base_lrc) # should not change since s1 is in the region. LRC should not be updated since s1 is in the region. + + @unittest.expectedFailure + def test_intersection_update_swap_bodies_different_region_2_iand(self): + """ + the first set is in the region, but the second set is in the local. Using &=. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.f = self.A() + original_lrc = r._lrc + r.arr1 = [r.a, r.b, r.c] + arr2 = [r.b, r.c, r.f] + self.assertEqual(r._lrc, original_lrc + 3) + + r.s1 = set(r.arr1) + s2 = set(arr2) + self.assertEqual(r._lrc, original_lrc + 3 + 3) + base_lrc = r._lrc + + r.s1 &= s2 + self.assertEqual(r._lrc, base_lrc) # should not change since s1 is in the region. LRC should not be updated since s1 is in the region. + + def test_intersection_update_multi_swap_bodies_different_region(self): + """ + the first and third set is in the region, but the second set is local. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.d = self.A() + r.e = self.A() + r.f = self.A() + original_lrc = r._lrc + r.arr1 = [r.a, r.b, r.c] + arr2 = [r.b, r.c, r.f] + r.arr3 = [r.b, r.d, r.e] + self.assertEqual(r._lrc, original_lrc + 3) + + r.s1 = set(r.arr1) + s2 = set(arr2) + r.s3 = set(r.arr3) + self.assertEqual(r._lrc, original_lrc + 3 + 3) + base_lrc = r._lrc + + r.s1.intersection_update(s2, r.s3) + self.assertEqual(r._lrc, base_lrc) # should not change LRC since s1 is in the region. LRC should not be updated + + +class TestRegionSetUnion(unittest.TestCase): + """Tests for set union operations and their effect on LRC.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_union_result_is_local(self): + """ + The result of a union of two sets borrowing from a region + should be a local set. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.f = self.A() + r.arr1 = [r.a, r.b, r.c] + r.arr2 = [r.b, r.c, r.f] + + s1 = set(r.arr1) + s2 = set(r.arr2) + + result = s1 | s2 + self.assertTrue(is_local(result)) + + def test_union_lrc_reflects_all_unique_elements(self): + """ + The union result holds references to all unique elements across + both sets, so the LRC should increase by the count of unique elements. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.f = self.A() + r.arr1 = [r.a, r.b, r.c] # {a, b, c} + r.arr2 = [r.b, r.c, r.f] # {b, c, f} + + s1 = set(r.arr1) + s2 = set(r.arr2) + base_lrc = r._lrc + + result = s1 | s2 # {a, b, c, f} + self.assertEqual(r._lrc, base_lrc + 4) + + result = None + self.assertEqual(r._lrc, base_lrc) + + def test_union_lrc_reflects_all_unique_elements_union(self): + """ + The union result holds references to all unique elements across + both sets, so the LRC should increase by the count of unique elements. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.f = self.A() + r.arr1 = [r.a, r.b, r.c] # {a, b, c} + r.arr2 = [r.b, r.c, r.f] # {b, c, f} + + s1 = set(r.arr1) + s2 = set(r.arr2) + base_lrc = r._lrc + + result = s1.union(s2) # {a, b, c, f} + self.assertEqual(r._lrc, base_lrc + 4) + + result = None + self.assertEqual(r._lrc, base_lrc) + + def test_union_operator_matches_method(self): + """ + The `|` operator should behave identically to union(). + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.f = self.A() + r.arr1 = [r.a, r.b, r.c] + r.arr2 = [r.b, r.c, r.f] + + s1 = set(r.arr1) + s2 = set(r.arr2) + + self.assertEqual(s1 | s2, s1.union(s2)) + + +class TestRegionSetUnionUpdate(unittest.TestCase): + """Tests for in-place union (|=) and LRC.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_union_update_adds_new_refs(self): + """ + |= should add references to new elements from s2 that + weren't already in s1, increasing the LRC accordingly. + Using |=. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.f = self.A() + r.arr1 = [r.a, r.b, r.c] # {a, b, c} + r.arr2 = [r.b, r.c, r.f] # {b, c, f} + + s1 = set(r.arr1) + s2 = set(r.arr2) + base_lrc = r._lrc + + s1 |= s2 # s1 becomes {a, b, c, f}, gains ref to f + self.assertEqual(r._lrc, base_lrc - 3 + 4) # -3 for original a b c, +4 for new a b c f (but a b c are still borrowed, so net +1 for f) + + def test_union_update_adds_new_refs_update(self): + """ + |= should add references to new elements from s2 that + weren't already in s1, increasing the LRC accordingly. + Using update instead of |=. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.f = self.A() + r.arr1 = [r.a, r.b, r.c] # {a, b, c} + r.arr2 = [r.b, r.c, r.f] # {b, c, f} + + s1 = set(r.arr1) + s2 = set(r.arr2) + base_lrc = r._lrc + + s1.update(s2) # s1 becomes {a, b, c, f}, gains ref to f + self.assertEqual(r._lrc, base_lrc - 3 + 4) # -3 for original a b c, +4 for new a b c f (but a b c are still borrowed, so net +1 for f) + + @unittest.expectedFailure + def test_union_update_adds_new_refs_2(self): + """ + |= should add references to new elements from s2 that + weren't already in s1, increasing the LRC accordingly. + Using |=. First set is in the region, second set is in local. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.f = self.A() + r.arr1 = [r.a, r.b, r.c] # {a, b, c} + r.arr2 = [r.b, r.c, r.f] # {b, c, f} + + r.s1 = set(r.arr1) + s2 = set(r.arr2) + base_lrc = r._lrc + + r.s1 |= s2 + self.assertEqual(r._lrc, base_lrc) + + def test_union_update_adds_new_refs_2_update(self): + """ + |= should add references to new elements from s2 that + weren't already in s1, increasing the LRC accordingly. + Using update instead of |=. First set is in the region, second set is in local. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.f = self.A() + r.arr1 = [r.a, r.b, r.c] # {a, b, c} + r.arr2 = [r.b, r.c, r.f] # {b, c, f} + + r.s1 = set(r.arr1) + s2 = set(r.arr2) + base_lrc = r._lrc + + r.s1.update(s2) + self.assertEqual(r._lrc, base_lrc) + + def test_union_update_released_decreases_lrc(self): + """ + Releasing s1 after |= should drop all the references it holds. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.f = self.A() + r.arr1 = [r.a, r.b, r.c] + r.arr2 = [r.b, r.c, r.f] + + s2 = set(r.arr2) + base_lrc = r._lrc + + s1 = set(r.arr1) + s1 |= s2 + s1 = None + # All s1 refs released, only s2's refs remain + self.assertEqual(r._lrc, base_lrc) + + +class TestRegionSetDifferenceUpdate(unittest.TestCase): + """Tests for in-place difference (-=) and LRC.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_difference_update_removes_refs(self): + """ + -= should release references to elements removed from s1, + decreasing the LRC by the number of elements subtracted. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr1 = [r.a, r.b, r.c] # {a, b, c} + r.arr2 = [r.a] # {a} + + s1 = set(r.arr1) + s2 = set(r.arr2) + base_lrc = r._lrc + + s1 -= s2 # s1 becomes {b, c}, drops ref to a + self.assertEqual(r._lrc, base_lrc - 3 + 2) # -3 for original a b c, +2 for remaining b and c + + def test_difference_update_removes_refs_difference_update(self): + """ + -= should release references to elements removed from s1, + decreasing the LRC by the number of elements subtracted. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr1 = [r.a, r.b, r.c] # {a, b, c} + r.arr2 = [r.a] # {a} + + s1 = set(r.arr1) + s2 = set(r.arr2) + base_lrc = r._lrc + + s1.difference_update(s2) # s1 becomes {b, c}, drops ref to a + self.assertEqual(r._lrc, base_lrc - 3 + 2) # -3 for original a b c, +2 for remaining b and c + + @unittest.expectedFailure + def test_difference_update_removes_refs_2(self): + """ + -= should release references to elements removed from s1, + decreasing the LRC by the number of elements subtracted. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr1 = [r.a, r.b, r.c] # {a, b, c} + r.arr2 = [r.a] # {a} + + r.s1 = set(r.arr1) + s2 = set(r.arr2) + base_lrc = r._lrc + + r.s1 -= s2 # s1 becomes {b, c}, drops ref to a + self.assertEqual(r._lrc, base_lrc) # -3 for original a b c, +2 for remaining b and c + + def test_difference_update_removes_refs_difference_2_update(self): + """ + -= should release references to elements removed from s1, + decreasing the LRC by the number of elements subtracted. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr1 = [r.a, r.b, r.c] # {a, b, c} + r.arr2 = [r.a] # {a} + + r.s1 = set(r.arr1) + s2 = set(r.arr2) + base_lrc = r._lrc + + r.s1.difference_update(s2) # s1 becomes {b, c}, drops ref to a + self.assertEqual(r._lrc, base_lrc) + + def test_difference_update_result_released(self): + """ + Releasing s1 after -= should drop all remaining references it holds. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr1 = [r.a, r.b, r.c] + r.arr2 = [r.a] + + s2 = set(r.arr2) + base_lrc = r._lrc + + s1 = set(r.arr1) + s1 -= s2 + s1 = None + # Only s2's ref to a remains + self.assertEqual(r._lrc, base_lrc) + + +class TestRegionSetSymmetricDifferenceUpdate(unittest.TestCase): + """Tests for in-place symmetric difference (^= / symmetric_difference_update) and LRC.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_symmetric_difference_update_adjusts_lrc(self): + """ + symmetric_difference_update should release refs to common elements + and add refs to new unique elements from s2. + arr1 = {a, b, c, f}, arr2 = {a, b, f} → result = {c} + Drops a, b, f (3 refs), keeps c (1 ref). + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.f = self.A() + r.arr1 = [r.a, r.b, r.c, r.f] # {a, b, c, f} + r.arr2 = [r.a, r.b, r.f] # {a, b, f} + + s1 = set(r.arr1) + s2 = set(r.arr2) + base_lrc = r._lrc + + s1.symmetric_difference_update(s2) # s1 becomes {c} + self.assertEqual(r._lrc, base_lrc - 4 + 1) # -4 for original a b c f, +1 for remaining c + + def test_symmetric_difference_update_operator_matches_method(self): + """ + The `^=` operator should behave identically to symmetric_difference_update(). + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.f = self.A() + r.arr1 = [r.a, r.b, r.c, r.f] + r.arr2 = [r.a, r.b, r.f] + + s1_method = set(r.arr1) + s1_operator = set(r.arr1) + s2 = set(r.arr2) + + s1_method.symmetric_difference_update(s2) + s1_operator ^= s2 + self.assertEqual(s1_method, s1_operator) + + @unittest.expectedFailure + def test_symmetric_difference_update_removes_refs_2(self): + """ + ^= should release references to elements removed from s1, + decreasing the LRC by the number of elements subtracted. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr1 = [r.a, r.b, r.c] # {a, b, c} + r.arr2 = [r.a] # {a} + + r.s1 = set(r.arr1) + s2 = set(r.arr2) + base_lrc = r._lrc + + r.s1 ^= s2 + self.assertEqual(r._lrc, base_lrc) + + def test_symmetric_difference_update_removes_refs_difference_2_update(self): + """ + ^= should release references to elements removed from s1, + decreasing the LRC by the number of elements subtracted. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr1 = [r.a, r.b, r.c] # {a, b, c} + r.arr2 = [r.a] # {a} + + r.s1 = set(r.arr1) + s2 = set(r.arr2) + base_lrc = r._lrc + + r.s1.symmetric_difference_update(s2) + self.assertEqual(r._lrc, base_lrc) + + +class TestRegionSetSubsetSuperset(unittest.TestCase): + """Tests for issubset / issuperset checks — these are read-only so LRC should not change.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_issubset_true(self): + """ + s1 = {a} is a subset of s2 = {a, b, c}. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr1 = [r.a] + r.arr2 = [r.a, r.b, r.c] + + s1 = set(r.arr1) + s2 = set(r.arr2) + + self.assertTrue(s1.issubset(s2)) + + def test_issubset_false(self): + """ + s1 = {a, b, c} is not a subset of s2 = {a}. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr1 = [r.a, r.b, r.c] + r.arr2 = [r.a] + + s1 = set(r.arr1) + s2 = set(r.arr2) + + self.assertFalse(s1.issubset(s2)) + + def test_issubset_operator_matches_method(self): + """ + The `<=` operator should behave identically to issubset(). + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr1 = [r.a] + r.arr2 = [r.a, r.b, r.c] + + s1 = set(r.arr1) + s2 = set(r.arr2) + + self.assertEqual(s1.issubset(s2), s1 <= s2) + + def test_issubset_does_not_change_lrc(self): + """ + issubset is a read-only operation and should not affect the LRC. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr1 = [r.a] + r.arr2 = [r.a, r.b, r.c] + + s1 = set(r.arr1) + s2 = set(r.arr2) + base_lrc = r._lrc + + _ = s1.issubset(s2) + self.assertEqual(r._lrc, base_lrc) + + def test_issuperset_true(self): + """ + s2 = {a, b, c} is a superset of s1 = {a}. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr1 = [r.a] + r.arr2 = [r.a, r.b, r.c] + + s1 = set(r.arr1) + s2 = set(r.arr2) + + self.assertTrue(s2.issuperset(s1)) + + def test_issuperset_false(self): + """ + s1 = {a} is not a superset of s2 = {a, b, c}. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr1 = [r.a] + r.arr2 = [r.a, r.b, r.c] + + s1 = set(r.arr1) + s2 = set(r.arr2) + + self.assertFalse(s1.issuperset(s2)) + + def test_issuperset_strict_operator(self): + """ + s2 > s1 (strict superset) should be False when s1 == s2. + """ + r = Region() + r.a = self.A() + r.arr1 = [r.a] + + s1 = set(r.arr1) + s2 = set(r.arr1) + + self.assertFalse(s2 > s1) + + def test_issuperset_does_not_change_lrc(self): + """ + issuperset is a read-only operation and should not affect the LRC. + """ + r = Region() + r.a = self.A() + r.b = self.A() + r.c = self.A() + r.arr1 = [r.a] + r.arr2 = [r.a, r.b, r.c] + + s1 = set(r.arr1) + s2 = set(r.arr2) + base_lrc = r._lrc + + _ = s2.issuperset(s1) + self.assertEqual(r._lrc, base_lrc) + + +class TestRegionSetIterator(unittest.TestCase): + """Tests for set iterator behavior and its effect on LRC.""" + + def setUp(self): + class A: pass + freeze(A()) + self.A = A + + def test_iter_creation_does_not_change_lrc(self): + """ + Creating an iterator over a set should not by itself + change the LRC of the region. + """ + r = Region() + r.word = self.A() + r.word2 = self.A() + r.arr = [r.word, r.word2] + + s = set(r.arr) + base_lrc = r._lrc + + it = iter(s) + self.assertEqual(r._lrc, base_lrc) + + def test_iter_creation_does_not_change_lrc_2(self): + """ + Creating an iterator over a set should not by itself + change the LRC of the region. + """ + r = Region() + r.word = self.A() + r.word2 = self.A() + r.arr = [r.word, r.word2] + + s = set(r.arr) + base_lrc = r._lrc + + r.it = iter(s) + self.assertEqual(r._lrc, base_lrc-1) + + def test_iter_creation_does_not_change_lrc_3(self): + """ + Creating an iterator over a set should not by itself + change the LRC of the region. + """ + r = Region() + r.word = self.A() + r.word2 = self.A() + r.arr = [r.word, r.word2] + + r.s = set(r.arr) + base_lrc = r._lrc + + r.it = iter(r.s) + self.assertEqual(r._lrc, base_lrc) + + def test_iter_creation_does_not_change_lrc_4(self): + """ + Creating an iterator over a set should not by itself + change the LRC of the region. + """ + r = Region() + r.word = self.A() + r.word2 = self.A() + r.arr = [r.word, r.word2] + + r.s = set(r.arr) + base_lrc = r._lrc + + it = iter(r.s) # iterator object points to the object in the region. + self.assertEqual(r._lrc, base_lrc+1) + + def test_next_on_iter_increases_lrc(self): + """ + Calling next() on the iterator yields a borrowed reference, + increasing the LRC by 1. + """ + r = Region() + r.word = self.A() + r.word2 = self.A() + r.word3 = self.A() + r.word4 = self.A() + r.arr = [r.word, r.word2, r.word3, r.word4] + + s = set(r.arr) + it = iter(s) + base_lrc = r._lrc + + a1 = next(it) + self.assertEqual(r._lrc, base_lrc + 1) + + a2 = next(it) + self.assertEqual(r._lrc, base_lrc + 2) + + r.a3 = next(it) + self.assertEqual(r._lrc, base_lrc + 2) + + a4 = next(it) + self.assertEqual(r._lrc, base_lrc + 3) + + def test_next_on_iter_increases_lrc_2(self): + """ + Calling next() on the iterator yields a borrowed reference, + increasing the LRC by 1. + """ + r = Region() + r.word = self.A() + r.word2 = self.A() + r.word3 = self.A() + r.word4 = self.A() + r.arr = [r.word, r.word2, r.word3, r.word4] + + s = set(r.arr) + r.it = iter(s) + base_lrc = r._lrc + + r.a1 = next(r.it) + self.assertEqual(r._lrc, base_lrc) + + r.a2 = next(r.it) + self.assertEqual(r._lrc, base_lrc) + + r.a3 = next(r.it) + self.assertEqual(r._lrc, base_lrc) + + r.a4 = next(r.it) + self.assertEqual(r._lrc, base_lrc) + + def test_iter_to_none_releases_lrc(self): + """ + Setting the iterator to None should release the iterator's + reference and bring LRC back to the pre-iterator level. + """ + r = Region() + r.word = self.A() + r.word2 = self.A() + r.arr = [r.word, r.word2] + + s = set(r.arr) + it = iter(s) + base_lrc = r._lrc + + it = None + self.assertEqual(r._lrc, base_lrc) + + def test_next_into_region_transfers_ownership(self): + """ + Assigning next() result into a region should transfer ownership + rather than keeping it as a borrowed external reference. + """ + r = Region() + r.word = self.A() + r.word2 = self.A() + r.word3 = self.A() + r.arr = [r.word, r.word2, r.word3] + + s = set(r.arr) + it = iter(s) + base_lrc = r._lrc + + a1 = next(it) # external local ref, LRC + 1 + r.a3 = next(it) # moved into region, no external borrow + self.assertEqual(r._lrc, base_lrc + 1) + + def test_iterator(self): + """ + Creating an iterator from a set that borrows from a region should + not increase the LRC, since the iterator itself does not hold + references to the elements (it borrows from the set). + """ + r = Region() + r.word = self.A() + r.word2 = self.A() + r.arr = [r.word, r.word2] + s = set(r.arr) + base_lrc = r._lrc + + it0 = iter(s) + self.assertEqual(r._lrc, base_lrc) + r.it = iter(s) + self.assertEqual(r._lrc, base_lrc-1+1) # s is moved into the region, but now it0 points to iterator that is moved into the region. + it2 = iter(s) + self.assertEqual(r._lrc, base_lrc+1) + + def test_iterator2(self): + """ + Creating an iterator from a set that borrows from a region should + not increase the LRC, since the iterator itself does not hold + references to the elements (it borrows from the set). + """ + r = Region() + r.word = self.A() + r.word2 = self.A() + r.arr = [r.word, r.word2] + r.s = set(r.arr) + base_lrc = r._lrc + + it = iter(r.s) + self.assertEqual(r._lrc, base_lrc+1) # r.s is moved into the region. + it2 = iter(r.s) + self.assertEqual(r._lrc, base_lrc+2) + r.it3 = iter(r.s) + self.assertEqual(r._lrc, base_lrc+2) + + def test_iterator_on_iterator(self): + """ + Creating an iterator from a set that borrows from a region should + not increase the LRC, since the iterator itself does not hold + references to the elements (it borrows from the set). + """ + r = Region() + r.word = self.A() + r.word2 = self.A() + r.arr = [r.word, r.word2] + s = set(r.arr) + base_lrc = r._lrc + + r.it0 = iter(s) + self.assertEqual(r._lrc, base_lrc-1) + r.it = iter(r.it0) + self.assertEqual(r._lrc, base_lrc-1) + it2 = iter(r.it0) + self.assertEqual(r._lrc, base_lrc-1+1) + + def test_iterator_on_iterator_2(self): + """ + Creating an iterator from a set that borrows from a region should + not increase the LRC, since the iterator itself does not hold + references to the elements (it borrows from the set). + """ + r = Region() + r.word = self.A() + r.word2 = self.A() + r.arr = [r.word, r.word2] + r.s = set(r.arr) + base_lrc = r._lrc + + r.it0 = iter(r.s) + self.assertEqual(r._lrc, base_lrc) + r.it = iter(r.it0) + self.assertEqual(r._lrc, base_lrc) + it2 = iter(r.it0) + self.assertEqual(r._lrc, base_lrc+1) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/Modules/clinic/itertoolsmodule.c.h b/Modules/clinic/itertoolsmodule.c.h index 49816bfcb42fec..9e76ed6020e8b8 100644 --- a/Modules/clinic/itertoolsmodule.c.h +++ b/Modules/clinic/itertoolsmodule.c.h @@ -84,6 +84,7 @@ batched_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) PyObject *iobj = _PyNumber_Index(fastargs[1]); if (iobj != NULL) { ival = PyLong_AsSsize_t(iobj); + PyRegion_RemoveLocalRef(iobj); Py_DECREF(iobj); } if (ival == -1 && PyErr_Occurred()) { diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index 60ef6f9ff4cd98..1d097a3b16d955 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -157,10 +157,18 @@ batched_new_impl(PyTypeObject *type, PyObject *iterable, Py_ssize_t n, /* create batchedobject structure */ bo = (batchedobject *)type->tp_alloc(type, 0); if (bo == NULL) { + PyRegion_RemoveLocalRef(it); Py_DECREF(it); return NULL; } bo->batch_size = n; + if(PyRegion_TakeRef(bo, it)) { + PyRegion_RemoveLocalRef(bo); + PyRegion_RemoveLocalRef(it); + Py_DECREF(bo); + Py_DECREF(it); + return NULL; + } bo->it = it; bo->strict = (bool) strict; return (PyObject *)bo; @@ -172,8 +180,10 @@ batched_dealloc(PyObject *op) batchedobject *bo = batchedobject_CAST(op); PyTypeObject *tp = Py_TYPE(bo); PyObject_GC_UnTrack(bo); + PyRegion_RemoveRef(bo, bo->it); Py_XDECREF(bo->it); tp->tp_free(bo); + PyRegion_RemoveLocalRef(tp); Py_DECREF(tp); } @@ -220,8 +230,9 @@ batched_next(PyObject *op) /* Input raised an exception other than StopIteration */ FT_ATOMIC_STORE_SSIZE_RELAXED(bo->batch_size, -1); #ifndef Py_GIL_DISABLED - Py_CLEAR(bo->it); + PyRegion_CLEAR(bo, bo->it); #endif + PyRegion_RemoveLocalRef(result); Py_DECREF(result); return NULL; } @@ -230,16 +241,18 @@ batched_next(PyObject *op) if (i == 0) { FT_ATOMIC_STORE_SSIZE_RELAXED(bo->batch_size, -1); #ifndef Py_GIL_DISABLED - Py_CLEAR(bo->it); + PyRegion_CLEAR(bo, bo->it); #endif + PyRegion_RemoveLocalRef(result); Py_DECREF(result); return NULL; } if (bo->strict) { FT_ATOMIC_STORE_SSIZE_RELAXED(bo->batch_size, -1); #ifndef Py_GIL_DISABLED - Py_CLEAR(bo->it); + PyRegion_CLEAR(bo, bo->it); #endif + PyRegion_RemoveLocalRef(result); Py_DECREF(result); PyErr_SetString(PyExc_ValueError, "batched(): incomplete batch"); return NULL; @@ -305,6 +318,14 @@ pairwise_new_impl(PyTypeObject *type, PyObject *iterable) } po = (pairwiseobject *)type->tp_alloc(type, 0); if (po == NULL) { + PyRegion_RemoveLocalRef(it); + Py_DECREF(it); + return NULL; + } + if(PyRegion_TakeRef(po, it)) { + PyRegion_RemoveLocalRef(po); + PyRegion_RemoveLocalRef(it); + Py_DECREF(po); Py_DECREF(it); return NULL; } @@ -312,6 +333,7 @@ pairwise_new_impl(PyTypeObject *type, PyObject *iterable) po->old = NULL; po->result = PyTuple_Pack(2, Py_None, Py_None); if (po->result == NULL) { + PyRegion_RemoveLocalRef(po); Py_DECREF(po); return NULL; } @@ -324,10 +346,14 @@ pairwise_dealloc(PyObject *op) pairwiseobject *po = pairwiseobject_CAST(op); PyTypeObject *tp = Py_TYPE(po); PyObject_GC_UnTrack(po); + PyRegion_RemoveRef(po, po->it); + PyRegion_RemoveRef(po, po->old); + PyRegion_RemoveRef(po, po->result); Py_XDECREF(po->it); Py_XDECREF(po->old); Py_XDECREF(po->result); tp->tp_free(po); + PyRegion_RemoveLocalRef(tp); Py_DECREF(tp); } @@ -348,57 +374,106 @@ pairwise_next(PyObject *op) pairwiseobject *po = pairwiseobject_CAST(op); PyObject *it = po->it; PyObject *old = po->old; - PyObject *new, *result; + PyObject *new; + PyObject *result = po->result; if (it == NULL) { return NULL; } if (old == NULL) { old = (*Py_TYPE(it)->tp_iternext)(it); - Py_XSETREF(po->old, old); + if(PyRegion_XSETREF(po, po->old, old)) { + return NULL; + } + // Py_XSETREF(po->old, old); if (old == NULL) { - Py_CLEAR(po->it); + PyRegion_CLEAR(po, po->it); return NULL; } it = po->it; if (it == NULL) { - Py_CLEAR(po->old); + PyRegion_CLEAR(po, po->old); return NULL; } } + PyRegion_AddLocalRef(old); Py_INCREF(old); new = (*Py_TYPE(it)->tp_iternext)(it); if (new == NULL) { - Py_CLEAR(po->it); - Py_CLEAR(po->old); + PyRegion_CLEAR(po, po->it); + PyRegion_CLEAR(po, po->old); + PyRegion_RemoveLocalRef(old); Py_DECREF(old); return NULL; } - result = po->result; - if (Py_REFCNT(result) == 1) { - Py_INCREF(result); - PyObject *last_old = PyTuple_GET_ITEM(result, 0); - PyObject *last_new = PyTuple_GET_ITEM(result, 1); - PyTuple_SET_ITEM(result, 0, Py_NewRef(old)); - PyTuple_SET_ITEM(result, 1, Py_NewRef(new)); - Py_DECREF(last_old); - Py_DECREF(last_new); - // bpo-42536: The GC may have untracked this result tuple. Since we're - // recycling it, make sure it's tracked again: - _PyTuple_Recycle(result); + if(result != NULL) { + if (Py_REFCNT(result) == 1 && PyRegion_IsLocal(result)) { + if(PyRegion_AddLocalRef(result)) { + PyRegion_RemoveLocalRef(old); + PyRegion_RemoveLocalRef(new); + Py_DECREF(old); + Py_DECREF(new); + return NULL; + } + Py_INCREF(result); + PyObject *last_old = PyTuple_GET_ITEM(result, 0); + PyObject *last_new = PyTuple_GET_ITEM(result, 1); + PyObject *old_region = PyRegion_NewRef(old); + PyObject *new_region = PyRegion_NewRef(new); + if(PyRegion_TakeRefs(result, old_region, new_region)) { + PyRegion_RemoveLocalRef(old_region); + PyRegion_RemoveLocalRef(new_region); + Py_DECREF(old_region); + Py_DECREF(new_region); + PyRegion_RemoveLocalRef(result); + Py_DECREF(result); + return NULL; + } + PyTuple_SET_ITEM(result, 0, old_region); + PyTuple_SET_ITEM(result, 1, new_region); + PyRegion_RemoveLocalRef(last_old); + Py_DECREF(last_old); + PyRegion_RemoveLocalRef(last_new); + Py_DECREF(last_new); + // bpo-42536: The GC may have untracked this result tuple. Since we're + // recycling it, make sure it's tracked again: + _PyTuple_Recycle(result); + goto end; + } + else { + PyRegion_CLEAR(po, po->result); + } } - else { - result = PyTuple_New(2); - if (result != NULL) { - PyTuple_SET_ITEM(result, 0, Py_NewRef(old)); - PyTuple_SET_ITEM(result, 1, Py_NewRef(new)); + + result = PyTuple_New(2); + if (result != NULL) { + PyObject *old_region = PyRegion_NewRef(old); + PyObject *new_region = PyRegion_NewRef(new); + if(PyRegion_TakeRefs(result, old_region, new_region)) { + PyRegion_RemoveLocalRef(old_region); + PyRegion_RemoveLocalRef(new_region); + Py_DECREF(old_region); + Py_DECREF(new_region); + PyRegion_RemoveLocalRef(result); + Py_DECREF(result); + return NULL; } + PyTuple_SET_ITEM(result, 0, old_region); + PyTuple_SET_ITEM(result, 1, new_region); + goto end; } - Py_XSETREF(po->old, new); - Py_DECREF(old); - return result; + end: + if(PyRegion_XSETREF(po, po->old, new)) { + PyRegion_RemoveLocalRef(new); + Py_DECREF(new); + return NULL; + } + // Py_XSETREF(po->old, new); + PyRegion_RemoveLocalRef(old); + Py_DECREF(old); + return result; } static PyType_Slot pairwise_slots[] = { @@ -4004,6 +4079,28 @@ static int itertoolsmodule_clear(PyObject *mod) { itertools_state *state = get_module_state(mod); + PyRegion_RemoveLocalRef(state->accumulate_type); + PyRegion_RemoveLocalRef(state->batched_type); + PyRegion_RemoveLocalRef(state->chain_type); + PyRegion_RemoveLocalRef(state->combinations_type); + PyRegion_RemoveLocalRef(state->compress_type); + PyRegion_RemoveLocalRef(state->count_type); + PyRegion_RemoveLocalRef(state->cwr_type); + PyRegion_RemoveLocalRef(state->cycle_type); + PyRegion_RemoveLocalRef(state->dropwhile_type); + PyRegion_RemoveLocalRef(state->filterfalse_type); + PyRegion_RemoveLocalRef(state->groupby_type); + PyRegion_RemoveLocalRef(state->_grouper_type); + PyRegion_RemoveLocalRef(state->islice_type); + PyRegion_RemoveLocalRef(state->pairwise_type); + PyRegion_RemoveLocalRef(state->permutations_type); + PyRegion_RemoveLocalRef(state->product_type); + PyRegion_RemoveLocalRef(state->repeat_type); + PyRegion_RemoveLocalRef(state->starmap_type); + PyRegion_RemoveLocalRef(state->takewhile_type); + PyRegion_RemoveLocalRef(state->tee_type); + PyRegion_RemoveLocalRef(state->teedataobject_type); + PyRegion_RemoveLocalRef(state->ziplongest_type); Py_CLEAR(state->accumulate_type); Py_CLEAR(state->batched_type); Py_CLEAR(state->chain_type); diff --git a/Objects/abstract.c b/Objects/abstract.c index ca3d013593388e..b20309e7d6da75 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -1183,6 +1183,7 @@ PyNumber_Add(PyObject *v, PyObject *w) if (result != Py_NotImplemented) { return result; } + assert(PyRegion_IsLocal(result)); Py_DECREF(result); PySequenceMethods *m = Py_TYPE(v)->tp_as_sequence; @@ -1466,7 +1467,14 @@ _PyNumber_Index(PyObject *item) } if (PyLong_Check(item)) { - return Py_NewRef(item); + // PyRegion_AddLocalRef(item); // Can Fail + /* + if (PyRegion_AddLocalRef(item)) { + return NULL; + } + return Py_NewRef(item); + */ + return PyRegion_NewRef(item); } if (!_PyIndex_Check(item)) { PyErr_Format(PyExc_TypeError, @@ -1508,6 +1516,7 @@ PyObject * PyNumber_Index(PyObject *item) { PyObject *result = _PyNumber_Index(item); + // For subclass handling if (result != NULL && !PyLong_CheckExact(result)) { Py_SETREF(result, _PyLong_Copy((PyLongObject *)result)); } @@ -1562,6 +1571,7 @@ PyNumber_AsSsize_t(PyObject *item, PyObject *err) } finish: + PyRegion_RemoveLocalRef(value); Py_DECREF(value); return result; } @@ -2257,6 +2267,7 @@ _PySequence_IterSearch(PyObject *seq, PyObject *obj, int operation) } cmp = PyObject_RichCompareBool(item, obj, Py_EQ); + PyRegion_RemoveLocalRef(item); Py_DECREF(item); if (cmp < 0) goto Fail; @@ -2305,6 +2316,7 @@ _PySequence_IterSearch(PyObject *seq, PyObject *obj, int operation) n = -1; /* fall through */ Done: + PyRegion_RemoveLocalRef(it); Py_DECREF(it); return n; @@ -2911,6 +2923,7 @@ PyObject_GetIter(PyObject *o) PyErr_Format(PyExc_TypeError, "%T.__iter__() must return an iterator, not %T", o, res); + // ASK FRED: NO BARRIER NEEDED here, right? Py_SETREF(res, NULL); } return res; diff --git a/Objects/enumobject.c b/Objects/enumobject.c index 814ce4f919514b..25d50be6ff44c4 100644 --- a/Objects/enumobject.c +++ b/Objects/enumobject.c @@ -56,6 +56,7 @@ enum_new_impl(PyTypeObject *type, PyObject *iterable, PyObject *start) if (start != NULL) { start = PyNumber_Index(start); if (start == NULL) { + PyRegion_RemoveLocalRef(en); Py_DECREF(en); return NULL; } @@ -64,22 +65,41 @@ enum_new_impl(PyTypeObject *type, PyObject *iterable, PyObject *start) if (en->en_index == -1 && PyErr_Occurred()) { PyErr_Clear(); en->en_index = PY_SSIZE_T_MAX; + if(PyRegion_TakeRef(en, start)) { + PyRegion_RemoveLocalRef(en); + Py_DECREF(en); + return NULL; + } en->en_longindex = start; } else { en->en_longindex = NULL; + PyRegion_RemoveLocalRef(start); Py_DECREF(start); } } else { en->en_index = 0; en->en_longindex = NULL; } - en->en_sit = PyObject_GetIter(iterable); + + // Internally call PyObject_SelfIter having PyRegion_NewRef, so TakeRef should be used + PyObject* itt = PyObject_GetIter(iterable); + if(PyRegion_TakeRef(en, itt)) { + PyRegion_RemoveLocalRef(en); + Py_DECREF(en); + return NULL; + } + en->en_sit = itt; if (en->en_sit == NULL) { + PyRegion_RemoveLocalRef(en); Py_DECREF(en); return NULL; } - en->en_result = PyTuple_Pack(2, Py_None, Py_None); + + PyObject* new_tuple = PyTuple_Pack(2, Py_None, Py_None); + assert(PyRegion_IsLocal(en) && PyRegion_IsLocal(new_tuple)); + en->en_result = new_tuple; if (en->en_result == NULL) { + PyRegion_RemoveLocalRef(en); Py_DECREF(en); return NULL; } @@ -157,6 +177,9 @@ enum_dealloc(PyObject *op) { enumobject *en = _enumobject_CAST(op); PyObject_GC_UnTrack(en); + PyRegion_RemoveRef(en, en->en_sit); + PyRegion_RemoveRef(en, en->en_result); + PyRegion_RemoveRef(en, en->en_longindex); Py_XDECREF(en->en_sit); Py_XDECREF(en->en_result); Py_XDECREF(en->en_longindex); @@ -190,6 +213,7 @@ increment_longindex_lock_held(enumobject *en) if (stepped_up == NULL) { return NULL; } + // ASK FRED: Do I need TakeRef here? en->en_longindex = stepped_up; return next_index; } @@ -207,25 +231,46 @@ enum_next_long(enumobject *en, PyObject* next_item) next_index = increment_longindex_lock_held(en); Py_END_CRITICAL_SECTION(); if (next_index == NULL) { + PyRegion_RemoveLocalRef(next_item); Py_DECREF(next_item); return NULL; } - if (_PyObject_IsUniquelyReferenced(result)) { - Py_INCREF(result); - old_index = PyTuple_GET_ITEM(result, 0); - old_item = PyTuple_GET_ITEM(result, 1); - PyTuple_SET_ITEM(result, 0, next_index); - PyTuple_SET_ITEM(result, 1, next_item); - Py_DECREF(old_index); - Py_DECREF(old_item); - // bpo-42536: The GC may have untracked this result tuple. Since we're - // recycling it, make sure it's tracked again: - _PyTuple_Recycle(result); - return result; + if(result != NULL) { + if (_PyObject_IsUniquelyReferenced(result) && PyRegion_IsLocal(result)) { + if(PyRegion_AddLocalRef(result)){ + PyRegion_RemoveLocalRef(next_item); + PyRegion_RemoveLocalRef(next_index); + Py_DECREF(next_index); + Py_DECREF(next_item); + return NULL; + } + Py_INCREF(result); + old_index = PyTuple_GET_ITEM(result, 0); + old_item = PyTuple_GET_ITEM(result, 1); + PyTuple_SET_ITEM(result, 0, next_index); + PyTuple_SET_ITEM(result, 1, next_item); + PyRegion_RemoveLocalRef(old_index); + PyRegion_RemoveLocalRef(old_item); + Py_DECREF(old_index); + Py_DECREF(old_item); + // bpo-42536: The GC may have untracked this result tuple. Since we're + // recycling it, make sure it's tracked again: + _PyTuple_Recycle(result); + return result; + } + else { + // PyRegion_RemoveRef(en, result); + // Py_DECREF(result); + // en->en_result = NULL; + PyRegion_CLEAR(en, en->en_result); + } } + result = PyTuple_New(2); if (result == NULL) { + PyRegion_RemoveLocalRef(next_index); + PyRegion_RemoveLocalRef(next_item); Py_DECREF(next_index); Py_DECREF(next_item); return NULL; @@ -256,26 +301,47 @@ enum_next(PyObject *op) next_index = PyLong_FromSsize_t(en_index); if (next_index == NULL) { + PyRegion_RemoveLocalRef(next_item); Py_DECREF(next_item); return NULL; } FT_ATOMIC_STORE_SSIZE_RELAXED(en->en_index, en_index + 1); - if (_PyObject_IsUniquelyReferenced(result)) { - Py_INCREF(result); - old_index = PyTuple_GET_ITEM(result, 0); - old_item = PyTuple_GET_ITEM(result, 1); - PyTuple_SET_ITEM(result, 0, next_index); - PyTuple_SET_ITEM(result, 1, next_item); - Py_DECREF(old_index); - Py_DECREF(old_item); - // bpo-42536: The GC may have untracked this result tuple. Since we're - // recycling it, make sure it's tracked again: - _PyTuple_Recycle(result); - return result; + if(result != NULL) { + if (_PyObject_IsUniquelyReferenced(result) && PyRegion_IsLocal(result)) { + if(PyRegion_AddLocalRef(result)){ + PyRegion_RemoveLocalRef(next_item); + PyRegion_RemoveLocalRef(next_index); + Py_DECREF(next_index); + Py_DECREF(next_item); + return NULL; + } + Py_INCREF(result); + old_index = PyTuple_GET_ITEM(result, 0); + old_item = PyTuple_GET_ITEM(result, 1); + PyTuple_SET_ITEM(result, 0, next_index); + PyTuple_SET_ITEM(result, 1, next_item); + PyRegion_RemoveLocalRef(old_index); + PyRegion_RemoveLocalRef(old_item); + Py_DECREF(old_index); + Py_DECREF(old_item); + // bpo-42536: The GC may have untracked this result tuple. Since we're + // recycling it, make sure it's tracked again: + _PyTuple_Recycle(result); + return result; + } + else { + // PyRegion_RemoveRef(en, result); + // Py_DECREF(result); + // en->en_result = NULL; + PyRegion_CLEAR(en, en->en_result); + } } + result = PyTuple_New(2); if (result == NULL) { + PyRegion_RemoveLocalRef(next_index); + PyRegion_RemoveLocalRef(next_item); Py_DECREF(next_index); Py_DECREF(next_item); return NULL; diff --git a/Objects/object.c b/Objects/object.c index 53502a0785b282..32efedfda22935 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1595,7 +1595,7 @@ _PyObject_GetDictPtr(PyObject *obj) PyObject * PyObject_SelfIter(PyObject *obj) { - return Py_NewRef(obj); + return PyRegion_NewRef(obj); } /* Helper used when the __next__ method is removed from a type: diff --git a/Objects/rangeobject.c b/Objects/rangeobject.c index a4b170c368747d..93297d1f95bf32 100644 --- a/Objects/rangeobject.c +++ b/Objects/rangeobject.c @@ -39,6 +39,7 @@ validate_step(PyObject *step) if (step && _PyLong_IsZero((PyLongObject *)step)) { PyErr_SetString(PyExc_ValueError, "range() arg 3 must not be zero"); + PyRegion_RemoveLocalRef(step); Py_CLEAR(step); } @@ -61,10 +62,19 @@ make_range_object(PyTypeObject *type, PyObject *start, if (obj == NULL) { obj = PyObject_New(rangeobject, type); if (obj == NULL) { + PyRegion_RemoveLocalRef(length); Py_DECREF(length); return NULL; } } + if(PyRegion_TakeRefs(obj, start, stop, step, length)) { + assert(PyRegion_IsLocal(obj)); // No write barrier bc obj is newly created and being in the local region + Py_DECREF(obj); + PyRegion_RemoveLocalRef(length); + Py_DECREF(length); + return NULL; + } + obj->start = start; obj->stop = stop; obj->step = step; @@ -82,6 +92,7 @@ range_from_array(PyTypeObject *type, PyObject *const *args, Py_ssize_t num_args) { rangeobject *obj; PyObject *start = NULL, *stop = NULL, *step = NULL; + // PyObject *startLog = NULL, *stopLog = NULL; // For Debug extra +1 of "step" switch (num_args) { case 3: @@ -95,11 +106,18 @@ range_from_array(PyTypeObject *type, PyObject *const *args, Py_ssize_t num_args) } stop = PyNumber_Index(args[1]); if (!stop) { + PyRegion_RemoveLocalRef(start); // Since start has already increased the _lrc but stop failed to convert, we need to remove the local ref for start to avoid refcount issues. + // RemoveLocalRef before Py_DECREF to prevent the object to be deallocated before we remove the local ref, which can cause refcount issues. Py_DECREF(start); return NULL; } - step = validate_step(step); /* Caution, this can clear exceptions */ + step = validate_step(step); /* Caution, this can clear exceptions */ + /* Also have Py_INCREF inside in the form of Py_NEWREF*/ + // validate_step already handles the case that no step is provided, so we don't need to handle that case here. + // This if-statement handles only the case that step is provided but invalid (e.g. step is 0 or step is not an integer). if (!step) { + PyRegion_RemoveLocalRef(start); // Since start has already increased the _lrc but step failed to convert, we need to remove the local ref for start to avoid refcount issues. + PyRegion_RemoveLocalRef(stop); // Since stop has already increased the _lrc but step failed to convert, we need to remove the local ref for stop to avoid refcount issues. Py_DECREF(start); Py_DECREF(stop); return NULL; @@ -129,9 +147,15 @@ range_from_array(PyTypeObject *type, PyObject *const *args, Py_ssize_t num_args) } /* Failed to create object, release attributes */ + PyRegion_RemoveLocalRef(start); + PyRegion_RemoveLocalRef(stop); + PyRegion_RemoveLocalRef(step); Py_DECREF(start); Py_DECREF(stop); Py_DECREF(step); + // PyRegion_CLEARLOCAL(start); + // PyRegion_CLEARLOCAL(stop); + // PyRegion_CLEARLOCAL(step); return NULL; } @@ -170,10 +194,14 @@ static void range_dealloc(PyObject *op) { rangeobject *r = (rangeobject*)op; - Py_DECREF(r->start); - Py_DECREF(r->stop); - Py_DECREF(r->step); - Py_DECREF(r->length); + PyRegion_CLEAR(r, r->start); + /* Equivalent to: + PyRegion_RemoveRef(r, r->start); + Py_DECREF(r->start); + */ + PyRegion_CLEAR(r, r->stop); + PyRegion_CLEAR(r, r->step); + PyRegion_CLEAR(r, r->length); _Py_FREELIST_FREE_OBJ(ranges, r, PyObject_Free); } @@ -262,6 +290,10 @@ compute_range_length(PyObject *start, PyObject *stop, PyObject *step) if (cmp_result == 1) { lo = start; hi = stop; + if(PyRegion_AddLocalRef(step)) // Should never fail, but just in case + { + return NULL; + } Py_INCREF(step); } else { lo = stop; @@ -274,6 +306,7 @@ compute_range_length(PyObject *start, PyObject *stop, PyObject *step) /* if (lo >= hi), return length of 0. */ cmp_result = PyObject_RichCompareBool(lo, hi, Py_GE); if (cmp_result != 0) { + PyRegion_RemoveLocalRef(step); Py_DECREF(step); if (cmp_result < 0) return NULL; @@ -295,11 +328,13 @@ compute_range_length(PyObject *start, PyObject *stop, PyObject *step) Py_DECREF(tmp2); Py_DECREF(diff); + PyRegion_RemoveLocalRef(step); Py_DECREF(step); Py_DECREF(tmp1); return result; Fail: + PyRegion_RemoveLocalRef(step); Py_DECREF(step); Py_XDECREF(tmp2); Py_XDECREF(diff); @@ -330,6 +365,7 @@ compute_item(rangeobject *r, PyObject *i) return NULL; } result = PyNumber_Add(r->start, incr); + assert(PyRegion_IsLocal(incr)); // incr should be local, so no write barrier here. Py_DECREF(incr); } return result; @@ -359,6 +395,9 @@ compute_range_item(rangeobject *r, PyObject *arg) return NULL; } } else { + if(PyRegion_AddLocalRef(arg)) { + return NULL; + } i = Py_NewRef(arg); } @@ -372,10 +411,12 @@ compute_range_item(rangeobject *r, PyObject *arg) cmp_result = PyObject_RichCompareBool(i, r->length, Py_GE); } if (cmp_result == -1) { - Py_DECREF(i); + PyRegion_RemoveLocalRef(i); + Py_DECREF(i); return NULL; } if (cmp_result == 1) { + PyRegion_RemoveLocalRef(i); Py_DECREF(i); PyErr_SetString(PyExc_IndexError, "range object index out of range"); @@ -383,10 +424,14 @@ compute_range_item(rangeobject *r, PyObject *arg) } result = compute_item(r, i); + PyRegion_RemoveLocalRef(i); Py_DECREF(i); return result; } +/* +Different from range_subscript since range_item accepts only Py_ssize_t, while range_subscript accepts any PyObject as the index. +*/ static PyObject * range_item(PyObject *op, Py_ssize_t i) { @@ -415,14 +460,17 @@ compute_slice(rangeobject *r, PyObject *_slice) substep = PyNumber_Multiply(r->step, step); if (substep == NULL) goto fail; + PyRegion_RemoveLocalRef(step); Py_CLEAR(step); substart = compute_item(r, start); if (substart == NULL) goto fail; + PyRegion_RemoveLocalRef(start); Py_CLEAR(start); substop = compute_item(r, stop); if (substop == NULL) goto fail; + PyRegion_RemoveLocalRef(stop); Py_CLEAR(stop); result = make_range_object(Py_TYPE(r), substart, substop, substep); @@ -430,6 +478,12 @@ compute_slice(rangeobject *r, PyObject *_slice) return (PyObject *) result; } fail: + PyRegion_RemoveLocalRef(start); + PyRegion_RemoveLocalRef(stop); + PyRegion_RemoveLocalRef(step); + PyRegion_RemoveLocalRef(substart); + PyRegion_RemoveLocalRef(substop); + PyRegion_RemoveLocalRef(substep); Py_XDECREF(start); Py_XDECREF(stop); Py_XDECREF(step); @@ -480,6 +534,8 @@ range_contains_long(rangeobject *r, PyObject *ob) /* result = ((int(ob) - start) % step) == 0 */ result = PyObject_RichCompareBool(tmp2, zero, Py_EQ); end: + assert(PyRegion_IsLocal(tmp1) || _Py_IsImmutable(tmp1)); // tmp1 should be local or immutable, so no write barrier here. + assert(PyRegion_IsLocal(tmp2) || _Py_IsImmutable(tmp2)); // tmp2 should be local or immutable, so no write barrier here. Py_XDECREF(tmp1); Py_XDECREF(tmp2); return result; @@ -586,28 +642,49 @@ range_hash(PyObject *op) t = PyTuple_New(3); if (!t) return -1; - PyTuple_SET_ITEM(t, 0, Py_NewRef(r->length)); + + // First element of the tuple is always the length of the range + PyObject* length = PyRegion_NewRef(r->length); + if(length == NULL) return -1; + PyTuple_SET_ITEM(t, 0, length); + + // len == 0 or not cmp_result = PyObject_Not(r->length); - if (cmp_result == -1) + if (cmp_result == -1) { goto end; + } + + // len is 0 if (cmp_result == 1) { PyTuple_SET_ITEM(t, 1, Py_NewRef(Py_None)); PyTuple_SET_ITEM(t, 2, Py_NewRef(Py_None)); } else { - PyTuple_SET_ITEM(t, 1, Py_NewRef(r->start)); + // Second element of the tuple is always the start of the range + PyObject* start = PyRegion_NewRef(r->start); + if(start == NULL) return -1; + PyTuple_SET_ITEM(t, 1, start); + + // Check if len == 1 or not cmp_result = PyObject_RichCompareBool(r->length, _PyLong_GetOne(), Py_EQ); if (cmp_result == -1) + { goto end; + } + // len is 1, step doesn't matter if (cmp_result == 1) { PyTuple_SET_ITEM(t, 2, Py_NewRef(Py_None)); } else { - PyTuple_SET_ITEM(t, 2, Py_NewRef(r->step)); + // The third element of the tuple is the step of the range when len > 1 + PyObject* step = PyRegion_NewRef(r->step); + if(step == NULL) return -1; + PyTuple_SET_ITEM(t, 2, step); } } result = PyObject_Hash(t); end: + PyRegion_RemoveLocalRef(t); Py_DECREF(t); return result; } @@ -660,6 +737,7 @@ range_index(PyObject *self, PyObject *ob) /* idx = (ob - r.start) // r.step */ PyObject *sidx = PyNumber_FloorDivide(idx, r->step); + assert(PyRegion_IsLocal(idx) || _Py_IsImmutable(idx)); // idx should be local, so no write barrier here. Py_DECREF(idx); return sidx; } @@ -715,11 +793,14 @@ range_subscript(PyObject *op, PyObject *item) { rangeobject *self = (rangeobject*)op; if (_PyIndex_Check(item)) { + // No change needed even if the PyNumber_Index has PyRegion_NewRef. + // My guess: It does nothing if "item" is not in a region. PyObject *i, *result; i = PyNumber_Index(item); if (!i) return NULL; result = compute_range_item(self, i); + PyRegion_RemoveLocalRef(i); Py_DECREF(i); return result; } @@ -778,6 +859,21 @@ static PyMemberDef range_members[] = { {0} }; +static int +range_traverse(PyObject *self, visitproc visit, void *arg) +{ + rangeobject *r = (rangeobject *)self; + + // Py_VISIT is a macro that calls visit(object, arg) + // and returns early if visit() returns non-zero (error) + Py_VISIT(r->start); + Py_VISIT(r->stop); + Py_VISIT(r->step); + Py_VISIT(r->length); + + return 0; +} + PyTypeObject PyRange_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "range", /* Name of this type */ @@ -800,7 +896,7 @@ PyTypeObject PyRange_Type = { 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_SEQUENCE, /* tp_flags */ range_doc, /* tp_doc */ - 0, /* tp_traverse */ + range_traverse, /* tp_traverse */ 0, /* tp_clear */ range_richcompare, /* tp_richcompare */ 0, /* tp_weaklistoffset */ @@ -875,6 +971,9 @@ rangeiter_reduce(PyObject *op, PyObject *Py_UNUSED(ignored)) return Py_BuildValue("N(N)O", _PyEval_GetBuiltin(&_Py_ID(iter)), range, Py_None); err: + assert(PyRegion_IsLocal(start) || _Py_IsImmutable(start)); // start should be local or immutable, so no write barrier here. + assert(PyRegion_IsLocal(stop) || _Py_IsImmutable(stop)); + assert(PyRegion_IsLocal(step) || _Py_IsImmutable(step)); Py_XDECREF(start); Py_XDECREF(stop); Py_XDECREF(step); @@ -904,6 +1003,14 @@ rangeiter_dealloc(PyObject *self) _Py_FREELIST_FREE_OBJ(range_iters, (_PyRangeIterObject *)self, PyObject_Free); } +static int +rangeiter_traverse(PyObject *self, visitproc visit, void *arg) +{ + // _PyRangeIterObject holds only C longs (start, step, len), + // not PyObject* references, so there is nothing to visit. + return 0; +} + PyDoc_STRVAR(reduce_doc, "Return state information for pickling."); PyDoc_STRVAR(setstate_doc, "Set state information for unpickling."); @@ -937,7 +1044,7 @@ PyTypeObject PyRangeIter_Type = { 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ 0, /* tp_doc */ - 0, /* tp_traverse */ + rangeiter_traverse, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ @@ -1006,6 +1113,11 @@ static PyObject * longrangeiter_len(PyObject *op, PyObject *Py_UNUSED(ignored)) { longrangeiterobject *r = (longrangeiterobject*)op; + // 0 on success + if(PyRegion_AddLocalRef(r->len)) { + return NULL; + } + // We dont do any assignment here, so No AddRef Py_INCREF(r->len); return r->len; } @@ -1025,10 +1137,17 @@ longrangeiter_reduce(PyObject *op, PyObject *Py_UNUSED(ignored)) Py_DECREF(product); if (stop == NULL) return NULL; + if(PyRegion_AddLocalRefs(r->start, r->step)) { + Py_DECREF(stop); + return NULL; + } range = (PyObject*)make_range_object(&PyRange_Type, Py_NewRef(r->start), stop, Py_NewRef(r->step)); if (range == NULL) { + PyRegion_RemoveLocalRef(r->start); // Because r is has a relationship to r->start, so No RemoveRef here + PyRegion_RemoveLocalRef(r->step); Py_DECREF(r->start); + assert(PyRegion_IsLocal(stop) || _Py_IsImmutable(stop)); Py_DECREF(stop); Py_DECREF(r->step); return NULL; @@ -1064,6 +1183,7 @@ longrangeiter_setstate(PyObject *op, PyObject *state) if (product == NULL) return NULL; PyObject *new_start = PyNumber_Add(r->start, product); + assert(PyRegion_IsLocal(product) || _Py_IsImmutable(product)); Py_DECREF(product); if (new_start == NULL) return NULL; @@ -1073,8 +1193,19 @@ longrangeiter_setstate(PyObject *op, PyObject *state) return NULL; } PyObject *tmp = r->start; + if(PyRegion_TakeRef(r, new_start)) { + Py_DECREF(new_start); + Py_DECREF(new_len); + return NULL; + } r->start = new_start; - Py_SETREF(r->len, new_len); + if(PyRegion_XSETREF(r, r->len, new_len)) { + Py_DECREF(new_start); + Py_DECREF(new_len); + return NULL; + } + // Original: Py_SETREF(r->len, new_len); + PyRegion_RemoveRef(r, tmp); Py_DECREF(tmp); Py_RETURN_NONE; } @@ -1090,9 +1221,12 @@ static void longrangeiter_dealloc(PyObject *op) { longrangeiterobject *r = (longrangeiterobject*)op; - Py_XDECREF(r->start); - Py_XDECREF(r->step); - Py_XDECREF(r->len); + PyRegion_CLEAR(r, r->start); + PyRegion_CLEAR(r, r->step); + PyRegion_CLEAR(r, r->len); + // Py_XDECREF(r->start); + // Py_XDECREF(r->step); + // Py_XDECREF(r->len); PyObject_Free(r); } @@ -1109,15 +1243,34 @@ longrangeiter_next(PyObject *op) } PyObject *new_len = PyNumber_Subtract(r->len, _PyLong_GetOne()); if (new_len == NULL) { + assert(PyRegion_IsLocal(new_start) || _Py_IsImmutable(new_start)); Py_DECREF(new_start); return NULL; } PyObject *result = r->start; r->start = new_start; - Py_SETREF(r->len, new_len); + // r->len is not Local, so we need to use PyRegion_XSETREF to update it. + if(PyRegion_XSETREF(r, r->len, new_len)) { + Py_DECREF(new_start); + Py_DECREF(new_len); + return NULL; + } + // Original: Py_SETREF(r->len, new_len); return result; } +static int +longrangeiter_traverse(PyObject *self, visitproc visit, void *arg) +{ + longrangeiterobject *r = (longrangeiterobject *)self; + + Py_VISIT(r->start); + Py_VISIT(r->step); + Py_VISIT(r->len); + + return 0; +} + PyTypeObject PyLongRangeIter_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "longrange_iterator", /* tp_name */ @@ -1141,12 +1294,12 @@ PyTypeObject PyLongRangeIter_Type = { 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ 0, /* tp_doc */ - 0, /* tp_traverse */ + longrangeiter_traverse, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ PyObject_SelfIter, /* tp_iter */ - longrangeiter_next, /* tp_iternext */ + longrangeiter_next, /* tp_iternext */ /*next(it)*/ longrangeiter_methods, /* tp_methods */ 0, }; @@ -1200,6 +1353,10 @@ range_iter(PyObject *seq) if (it == NULL) return NULL; + if(PyRegion_AddRefs(it, r->start, r->step, r->length)) { + Py_DECREF(it); + return NULL; + } it->start = Py_NewRef(r->start); it->step = Py_NewRef(r->step); it->len = Py_NewRef(r->length); @@ -1283,6 +1440,10 @@ range_reverse(PyObject *seq, PyObject *Py_UNUSED(ignored)) it->start = it->step = NULL; /* start + (len - 1) * step */ + if(PyRegion_AddRef(it, range->length)) { + Py_DECREF(it); + return NULL; + } it->len = Py_NewRef(range->length); diff = PyNumber_Subtract(it->len, _PyLong_GetOne()); @@ -1307,6 +1468,7 @@ range_reverse(PyObject *seq, PyObject *Py_UNUSED(ignored)) return (PyObject *)it; create_failure: + PyRegion_RemoveLocalRef(it); Py_DECREF(it); return NULL; } diff --git a/Objects/setobject.c b/Objects/setobject.c index faf3bd34da8588..33bbebf9914ea2 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -45,6 +45,7 @@ #include "stringlib/eq.h" // unicode_eq() #include // offsetof() #include "clinic/setobject.c.h" +#include "region.h" /*[clinic input] class set "PySetObject *" "&PySet_Type" @@ -110,8 +111,12 @@ set_lookkey(PySetObject *so, PyObject *key, Py_hash_t hash) return NULL; } else { // incref startkey because it can be removed from the set by the compare + if(PyRegion_AddLocalRef(startkey)) { + return NULL; + } Py_INCREF(startkey); cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); + PyRegion_RemoveLocalRef(startkey); Py_DECREF(startkey); if (cmp < 0) return NULL; @@ -132,7 +137,7 @@ set_lookkey(PySetObject *so, PyObject *key, Py_hash_t hash) static int set_table_resize(PySetObject *, Py_ssize_t); static int -set_add_entry_takeref(PySetObject *so, PyObject *key, Py_hash_t hash) +set_add_entry_takeref(PySetObject *so, PyObject *key, Py_hash_t hash, bool take_ref) { setentry *table; setentry *freeslot; @@ -166,8 +171,12 @@ set_add_entry_takeref(PySetObject *so, PyObject *key, Py_hash_t hash) && unicode_eq(startkey, key)) goto found_active; table = so->table; + if(PyRegion_AddLocalRef(startkey)) { + return -1; + } Py_INCREF(startkey); cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); + PyRegion_RemoveLocalRef(startkey); Py_DECREF(startkey); if (cmp > 0) goto found_active; @@ -191,6 +200,12 @@ set_add_entry_takeref(PySetObject *so, PyObject *key, Py_hash_t hash) if (freeslot == NULL) goto found_unused; FT_ATOMIC_STORE_SSIZE_RELAXED(so->used, so->used + 1); + if (take_ref) { + if (PyRegion_TakeRef(so, key)) { + return -1; + } + } + // if(PyRegion_TakeRef(so, key)) return -1; freeslot->key = key; freeslot->hash = hash; return 0; @@ -198,6 +213,12 @@ set_add_entry_takeref(PySetObject *so, PyObject *key, Py_hash_t hash) found_unused: so->fill++; FT_ATOMIC_STORE_SSIZE_RELAXED(so->used, so->used + 1); + if (take_ref) { + if (PyRegion_TakeRef(so, key)) { + return -1; + } + } + // if(PyRegion_TakeRef(so, key)) return -1; entry->key = key; entry->hash = hash; if ((size_t)so->fill*5 < mask*3) @@ -205,10 +226,12 @@ set_add_entry_takeref(PySetObject *so, PyObject *key, Py_hash_t hash) return set_table_resize(so, so->used>50000 ? so->used*2 : so->used*4); found_active: + PyRegion_RemoveLocalRef(key); Py_DECREF(key); return 0; comparison_error: + PyRegion_RemoveLocalRef(key); Py_DECREF(key); return -1; } @@ -217,8 +240,10 @@ static int set_add_entry(PySetObject *so, PyObject *key, Py_hash_t hash) { _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(so); - - return set_add_entry_takeref(so, Py_NewRef(key), hash); + if (PyRegion_AddLocalRef(key)) { + return -1; + } + return set_add_entry_takeref(so, Py_NewRef(key), hash, true); } static void @@ -234,6 +259,7 @@ set_unhashable_type(PyObject *key) PyErr_Format(PyExc_TypeError, "cannot use '%T' as a set element (%S)", key, exc); + assert(PyRegion_IsLocal(exc)); Py_DECREF(exc); } @@ -248,7 +274,7 @@ _PySet_AddTakeRef(PySetObject *so, PyObject *key) } // We don't pre-increment here, the caller holds a strong // reference to the object which we are stealing. - return set_add_entry_takeref(so, key, hash); + return set_add_entry_takeref(so, key, hash, false); } /* @@ -259,8 +285,10 @@ a callback in the middle of a set_table_resize(), see issue 1456209. The caller is responsible for updating the key's reference count and the setobject's fill and used fields. */ -static void -set_insert_clean(setentry *table, size_t mask, PyObject *key, Py_hash_t hash) +static int +set_insert_clean( + PySetObject* set, setentry *table, size_t mask, + PyObject *key, Py_hash_t hash, bool take_ref) { setentry *entry; size_t perturb = hash; @@ -282,8 +310,14 @@ set_insert_clean(setentry *table, size_t mask, PyObject *key, Py_hash_t hash) i = (i * 5 + 1 + perturb) & mask; } found_null: + if (take_ref) { + if (PyRegion_TakeRef(set, key)) { + return -1; + } + } entry->key = key; entry->hash = hash; + return 0; } /* ======== End logic for probing the hash table ========================== */ @@ -356,14 +390,17 @@ set_table_resize(PySetObject *so, Py_ssize_t minused) if (so->fill == so->used) { for (entry = oldtable; entry <= oldtable + oldmask; entry++) { if (entry->key != NULL) { - set_insert_clean(newtable, newmask, entry->key, entry->hash); + // should never fail since it already has an element. + int res = set_insert_clean(so, newtable, newmask, entry->key, entry->hash, false); + assert(res==0); } } } else { so->fill = so->used; for (entry = oldtable; entry <= oldtable + oldmask; entry++) { if (entry->key != NULL && entry->key != dummy) { - set_insert_clean(newtable, newmask, entry->key, entry->hash); + int res = set_insert_clean(so, newtable, newmask, entry->key, entry->hash, false); + assert(res==0); } } } @@ -399,9 +436,12 @@ set_discard_entry(PySetObject *so, PyObject *key, Py_hash_t hash) if (entry->key == NULL) return DISCARD_NOTFOUND; old_key = entry->key; + // I don't think barrier should be handled for dummy entry->key = dummy; entry->hash = -1; FT_ATOMIC_STORE_SSIZE_RELAXED(so->used, so->used - 1); + // Use RemoveRef since old_key refers to "entry", which refers to "so" + PyRegion_RemoveRef(so, old_key); Py_DECREF(old_key); return DISCARD_FOUND; } @@ -497,6 +537,7 @@ set_clear_internal(PyObject *self) for (entry = table; used > 0; entry++) { if (entry->key && entry->key != dummy) { used--; + PyRegion_RemoveRef(so, entry->key); Py_DECREF(entry->key); } } @@ -557,6 +598,7 @@ set_dealloc(PyObject *self) for (entry = so->table; used > 0; entry++) { if (entry->key && entry->key != dummy) { used--; + PyRegion_RemoveRef(so, entry->key); Py_DECREF(entry->key); } } @@ -593,15 +635,23 @@ set_repr_lock_held(PySetObject *so) Py_ssize_t pos = 0, idx = 0; setentry *entry; while (set_next(so, &pos, &entry)) { - PyList_SET_ITEM(keys, idx++, Py_NewRef(entry->key)); + PyObject *entry_key = PyRegion_NewRef(entry->key); + if(entry_key == NULL) { + assert(PyRegion_IsLocal(keys)); + Py_DECREF(keys); + goto done; + } + PyList_SET_ITEM(keys, idx++, entry_key); } /* repr(keys)[1:-1] */ listrepr = PyObject_Repr(keys); + assert(PyRegion_IsLocal(keys)); Py_DECREF(keys); if (listrepr == NULL) goto done; tmp = PyUnicode_Substring(listrepr, 1, PyUnicode_GET_LENGTH(listrepr)-1); + assert(PyRegion_IsLocal(listrepr)); Py_DECREF(listrepr); if (tmp == NULL) goto done; @@ -613,6 +663,7 @@ set_repr_lock_held(PySetObject *so) listrepr); else result = PyUnicode_FromFormat("{%U}", listrepr); + assert(PyRegion_IsLocal(listrepr)); Py_DECREF(listrepr); done: Py_ReprLeave((PyObject*)so); @@ -668,6 +719,35 @@ set_merge_lock_held(PySetObject *so, PyObject *otherset) /* If our table is empty, and both tables have the same size, and there are no dummies to eliminate, then just copy the pointers. */ if (so->fill == 0 && so->mask == other->mask && other->fill == other->used) { + /* Collect keys into a temporary array for PyRegion_AddRefsArray */ + assert(other->table != NULL); + PyObject **keys_array = PyMem_New(PyObject*, other->used); + if (keys_array == NULL) { + PyErr_NoMemory(); + return -1; + } + /* First pass: collect keys */ + int keys_count = 0; + for (i = 0; i <= other->mask; i++) { + key = other->table[i].key; + if (key != NULL) { + keys_array[keys_count++] = key; + } + } + + /* Barrier before any key assignments */ + if (PyRegion_AddRefsArray(so, keys_count, keys_array)) { + PyMem_Free(keys_array); + return -1; + } + + PyMem_Free(keys_array); + + /* Reset pointers before second pass */ + so_entry = so->table; + other_entry = other->table; + + /* Second pass: copy pointers and hashes */ for (i = 0; i <= other->mask; i++, so_entry++, other_entry++) { key = other_entry->key; if (key != NULL) { @@ -690,8 +770,12 @@ set_merge_lock_held(PySetObject *so, PyObject *otherset) for (i = other->mask + 1; i > 0 ; i--, other_entry++) { key = other_entry->key; if (key != NULL && key != dummy) { - set_insert_clean(newtable, newmask, Py_NewRef(key), - other_entry->hash); + if(PyRegion_AddLocalRef(key)) { + return -1; + } + if(set_insert_clean(so, newtable, newmask, Py_NewRef(key),other_entry->hash, true)) { + return -1; + } } } return 0; @@ -742,10 +826,14 @@ set_pop_impl(PySetObject *so) entry = so->table; } key = entry->key; + if(PyRegion_AddLocalRef(key)) { // Bc key has to be returned, it is still there, maybe in or not in the region. (The reference the function returns) + return NULL; + } entry->key = dummy; entry->hash = -1; FT_ATOMIC_STORE_SSIZE_RELAXED(so->used, so->used - 1); so->finger = entry - so->table + 1; /* next place to start */ + PyRegion_RemoveRef(so, key); return key; } @@ -855,7 +943,8 @@ setiter_dealloc(PyObject *self) setiterobject *si = (setiterobject*)self; /* bpo-31095: UnTrack is needed before calling any callbacks */ _PyObject_GC_UNTRACK(si); - Py_XDECREF(si->si_set); + PyRegion_CLEAR(si, si->si_set); + // Py_XDECREF(si->si_set); PyObject_GC_Del(si); } @@ -886,10 +975,14 @@ setiter_reduce(PyObject *op, PyObject *Py_UNUSED(ignored)) /* copy the iterator state */ setiterobject tmp = *si; + if(PyRegion_AddLocalRef(tmp.si_set)) { + return NULL; + } Py_XINCREF(tmp.si_set); /* iterate the temporary into a list */ PyObject *list = PySequence_List((PyObject*)&tmp); + PyRegion_RemoveLocalRef(tmp.si_set); Py_XDECREF(tmp.si_set); if (list == NULL) { return NULL; @@ -935,13 +1028,23 @@ static PyObject *setiter_iternext(PyObject *self) i++; } if (i <= mask) { - key = Py_NewRef(entry[i].key); + // key = PyRegion_NewRef(entry[i].key); // Incorrect since we want to return NULL immediately, not set key to NULL. + // key = Py_NewRef(entry[i].key); + key = entry[i].key; + if (PyRegion_AddLocalRef(key)) { + return NULL; + } + Py_INCREF(key); } + // Cannot return before unlocking Py_END_CRITICAL_SECTION(); si->si_pos = i+1; if (key == NULL) { - si->si_set = NULL; - Py_DECREF(so); + PyRegion_CLEAR(si, si->si_set); + // Since so is si->si_set, PyRegion_RemoveRef should be used, not PyRegion_RemoveLocalRef + // PyRegion_RemoveRef(si, so); + // si->si_set = NULL; + // Py_DECREF(so); return NULL; } si->len--; @@ -954,7 +1057,7 @@ PyTypeObject PySetIter_Type = { sizeof(setiterobject), /* tp_basicsize */ 0, /* tp_itemsize */ /* methods */ - setiter_dealloc, /* tp_dealloc */ + setiter_dealloc, /* tp_dealloc */ // DONE 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ @@ -971,13 +1074,13 @@ PyTypeObject PySetIter_Type = { 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ 0, /* tp_doc */ - setiter_traverse, /* tp_traverse */ + setiter_traverse, /* tp_traverse */ // DONE 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ PyObject_SelfIter, /* tp_iter */ - setiter_iternext, /* tp_iternext */ - setiter_methods, /* tp_methods */ + setiter_iternext, /* tp_iternext */ // DONE + setiter_methods, /* tp_methods */ // DONE 0, }; @@ -988,6 +1091,10 @@ set_iter(PyObject *so) setiterobject *si = PyObject_GC_New(setiterobject, &PySetIter_Type); if (si == NULL) return NULL; + if(PyRegion_AddRef(si, so)) { + Py_DECREF(si); + return NULL; + } si->si_set = (PySetObject*)Py_NewRef(so); si->si_used = size; si->si_pos = 0; @@ -1040,12 +1147,16 @@ set_update_iterable_lock_held(PySetObject *so, PyObject *other) PyObject *key; while ((key = PyIter_Next(it)) != NULL) { if (set_add_key(so, key)) { + PyRegion_RemoveLocalRef(it); + PyRegion_RemoveLocalRef(key); Py_DECREF(it); Py_DECREF(key); return -1; } + PyRegion_RemoveLocalRef(key); Py_DECREF(key); } + PyRegion_RemoveLocalRef(it); Py_DECREF(it); if (PyErr_Occurred()) return -1; @@ -1167,6 +1278,7 @@ make_new_set(PyTypeObject *type, PyObject *iterable) if (iterable != NULL) { if (set_update_local(so, iterable)) { + PyRegion_RemoveLocalRef(so); Py_DECREF(so); return NULL; } @@ -1196,7 +1308,7 @@ make_new_frozenset(PyTypeObject *type, PyObject *iterable) if (iterable != NULL && PyFrozenSet_CheckExact(iterable)) { /* frozenset(f) is idempotent */ - return Py_NewRef(iterable); + return PyRegion_NewRef(iterable); } return make_new_set(type, iterable); } @@ -1251,38 +1363,83 @@ set_new(PyTypeObject *type, PyObject *args, PyObject *kwds) The function always succeeds and it leaves both objects in a stable state. Useful for operations that update in-place (by allowing an intermediate result to be swapped into one of the original inputs). + + This function is only used in set_intersection_update_multi_impl and set_intersection_update. + In other word, only .intersection_update and &= use this function. */ +static PyObject *set_difference(PySetObject *, PyObject *); // add this static void set_swap_bodies(PySetObject *a, PySetObject *b) { + /* + The object b is guarantee to be the local object, due to the origin from set_intersection_update_multi_impl and set_intersection_update. + If the object a is the local object as well, no barrier is needed. For example, + + r.arr1 = [r.a, r.b, r.c] + r.arr2 = [r.b, r.c, r.f] + s1 = set(r.arr1) + s2 = set(r.arr2) + + In set_intersection_update_multi_impl, tmp, which is b when it is passed to this function, points to r.b and r.c when being created, which increases LRC by 2. + After swapping, tmp points to r.a, r.b, and r.c. When tmp ref count is decreased to 0, LRC is also decreased by 3 from set_dealloc. + Finally, the result of s1.intersect_update(s2) seems as s1 releases the reference to r.a, which means LRC is finally decreased by one as expected. + + However, if s1 is in the region, which mean a is also in the region, not the local region anymore. + + r.arr1 = [r.a, r.b, r.c] + r.arr2 = [r.b, r.c, r.f] + r.s1 = set(r.arr1) + s2 = set(r.arr2) + + If there is no additional mechanism to handle LRC, the final LRC will be lower than it should be. + Without the mechanism, when tmp is created, LRC is increased by 2. Then, after swapping, when destroying tmp, LRC is decreased by 3. The final LRC is decreased by 1. + However, LRC should not be changed in this case because s1 is in the region. Releasing and pointing to the objects in the same region should not modify the LRC. + So, the solution is that after increasing LRC by 2 from creating tmp, which points to the intersection results, we also increase LRC by the size of "a-b", which is 1. + Then, we can use the original swap mechanism. Finally, when tmp is destroyed, LRC is decreased by 3. +3 and -3 becomes zero. Correct! + + */ + if(!PyRegion_IsLocal(a)) { + Py_ssize_t pos = 0; + setentry *entry; + PyObject *c = set_difference(a, b); + + while (set_next(c, &pos, &entry)) { + PyObject *key = entry->key; + int rv = PyRegion_AddLocalRef(key); // b now points to "key" because of the swap, and b is guaranteed to be in the local region, so we can add a local reference to key for b. + assert(rv==0); // assure that AddLocalRef cannot fail since key is already in the region. + PyRegion_RemoveRef(a, key); // Because key will be swapped, and b will be the parent instead. So, remove the relationship between a and key. + } + assert(PyRegion_IsLocal(c)); + Py_DECREF(c); // set_dealloc will remove all references from c to the objects in the set, and set_dealloc also handles LRC, so we don't have to do anything else to remove the references from the region here. + } + Py_ssize_t t; setentry *u; setentry tab[PySet_MINSIZE]; Py_hash_t h; - t = a->fill; a->fill = b->fill; b->fill = t; t = a->used; FT_ATOMIC_STORE_SSIZE_RELAXED(a->used, b->used); FT_ATOMIC_STORE_SSIZE_RELAXED(b->used, t); t = a->mask; a->mask = b->mask; b->mask = t; - + u = a->table; if (a->table == a->smalltable) - u = b->smalltable; + u = b->smalltable; a->table = b->table; if (b->table == b->smalltable) - a->table = a->smalltable; + a->table = a->smalltable; b->table = u; - + if (a->table == a->smalltable || b->table == b->smalltable) { memcpy(tab, a->smalltable, sizeof(tab)); memcpy(a->smalltable, b->smalltable, sizeof(tab)); memcpy(b->smalltable, tab, sizeof(tab)); } - + if (PyType_IsSubtype(Py_TYPE(a), &PyFrozenSet_Type) && - PyType_IsSubtype(Py_TYPE(b), &PyFrozenSet_Type)) { + PyType_IsSubtype(Py_TYPE(b), &PyFrozenSet_Type)) { h = FT_ATOMIC_LOAD_SSIZE_RELAXED(a->hash); FT_ATOMIC_STORE_SSIZE_RELAXED(a->hash, FT_ATOMIC_LOAD_SSIZE_RELAXED(b->hash)); FT_ATOMIC_STORE_SSIZE_RELAXED(b->hash, h); @@ -1310,6 +1467,7 @@ set_copy_impl(PySetObject *so) return NULL; } if (set_merge_lock_held((PySetObject *)copy, (PyObject *)so) < 0) { + PyRegion_RemoveLocalRef(copy); Py_DECREF(copy); return NULL; } @@ -1329,7 +1487,7 @@ frozenset_copy_impl(PySetObject *so) /*[clinic end generated code: output=b356263526af9e70 input=fbf5bef131268dd7]*/ { if (PyFrozenSet_CheckExact(so)) { - return Py_NewRef(so); + return PyRegion_NewRef(so); } return set_copy_impl(so); } @@ -1380,6 +1538,7 @@ set_union_impl(PySetObject *so, PyObject * const *others, if ((PyObject *)so == other) continue; if (set_update_local(result, other)) { + PyRegion_RemoveLocalRef(result); Py_DECREF(result); return NULL; } @@ -1403,6 +1562,7 @@ set_or(PyObject *self, PyObject *other) return (PyObject *)result; } if (set_update_local(result, other)) { + PyRegion_RemoveLocalRef(result); Py_DECREF(result); return NULL; } @@ -1419,7 +1579,7 @@ set_ior(PyObject *self, PyObject *other) if (set_update_internal(so, other)) { return NULL; } - return Py_NewRef(so); + return PyRegion_NewRef(so); } static PyObject * @@ -1450,20 +1610,30 @@ set_intersection(PySetObject *so, PyObject *other) while (set_next((PySetObject *)other, &pos, &entry)) { key = entry->key; hash = entry->hash; + if(PyRegion_AddLocalRef(key)) { + PyRegion_RemoveLocalRef(result); + Py_DECREF(result); + return NULL; + } Py_INCREF(key); rv = set_contains_entry(so, key, hash); if (rv < 0) { + PyRegion_RemoveLocalRef(result); + PyRegion_RemoveLocalRef(key); Py_DECREF(result); Py_DECREF(key); return NULL; } if (rv) { if (set_add_entry(result, key, hash)) { + PyRegion_RemoveLocalRef(result); + PyRegion_RemoveLocalRef(key); Py_DECREF(result); Py_DECREF(key); return NULL; } } + PyRegion_RemoveLocalRef(key); Py_DECREF(key); } return (PyObject *)result; @@ -1471,6 +1641,7 @@ set_intersection(PySetObject *so, PyObject *other) it = PyObject_GetIter(other); if (it == NULL) { + PyRegion_RemoveLocalRef(result); Py_DECREF(result); return NULL; } @@ -1486,19 +1657,26 @@ set_intersection(PySetObject *so, PyObject *other) if (set_add_entry(result, key, hash)) goto error; if (PySet_GET_SIZE(result) >= PySet_GET_SIZE(so)) { + PyRegion_RemoveLocalRef(key); Py_DECREF(key); break; } } + PyRegion_RemoveLocalRef(key); Py_DECREF(key); } + PyRegion_RemoveLocalRef(it); Py_DECREF(it); if (PyErr_Occurred()) { + PyRegion_RemoveLocalRef(result); Py_DECREF(result); return NULL; } return (PyObject *)result; error: + PyRegion_RemoveLocalRef(it); + PyRegion_RemoveLocalRef(key); + PyRegion_RemoveLocalRef(result); Py_DECREF(it); Py_DECREF(result); Py_DECREF(key); @@ -1524,6 +1702,9 @@ set_intersection_multi_impl(PySetObject *so, PyObject * const *others, return set_copy((PyObject *)so, NULL); } + if(PyRegion_AddLocalRef(so)) { + return NULL; + } PyObject *result = Py_NewRef(so); for (i = 0; i < others_length; i++) { PyObject *other = others[i]; @@ -1532,10 +1713,16 @@ set_intersection_multi_impl(PySetObject *so, PyObject * const *others, newresult = set_intersection((PySetObject *)result, other); Py_END_CRITICAL_SECTION2(); if (newresult == NULL) { + PyRegion_RemoveLocalRef(result); + Py_DECREF(result); + return NULL; + } + if(PyRegion_XSETLOCALREF(result, newresult)) { + PyRegion_RemoveLocalRef(result); Py_DECREF(result); return NULL; } - Py_SETREF(result, newresult); + // Py_SETREF(result, newresult); } return result; } @@ -1548,7 +1735,9 @@ set_intersection_update(PySetObject *so, PyObject *other) tmp = set_intersection(so, other); if (tmp == NULL) return NULL; + PyRegion_IsLocal(tmp); set_swap_bodies(so, (PySetObject *)tmp); + PyRegion_RemoveLocalRef(tmp); Py_DECREF(tmp); Py_RETURN_NONE; } @@ -1572,8 +1761,10 @@ set_intersection_update_multi_impl(PySetObject *so, PyObject * const *others, if (tmp == NULL) return NULL; Py_BEGIN_CRITICAL_SECTION(so); + PyRegion_IsLocal(tmp); set_swap_bodies(so, (PySetObject *)tmp); Py_END_CRITICAL_SECTION(); + PyRegion_RemoveLocalRef(tmp); Py_DECREF(tmp); Py_RETURN_NONE; } @@ -1608,8 +1799,10 @@ set_iand(PyObject *self, PyObject *other) if (result == NULL) return NULL; + PyRegion_RemoveLocalRef(result); Py_DECREF(result); - return Py_NewRef(so); + return PyRegion_NewRef(so); + // return Py_NewRef(so); } /*[clinic input] @@ -1647,8 +1840,12 @@ set_isdisjoint_impl(PySetObject *so, PyObject *other) } while (set_next((PySetObject *)other, &pos, &entry)) { PyObject *key = entry->key; + if(PyRegion_AddLocalRef(key)) { + return NULL; + } Py_INCREF(key); rv = set_contains_entry(so, key, entry->hash); + PyRegion_RemoveLocalRef(key); Py_DECREF(key); if (rv < 0) { return NULL; @@ -1666,16 +1863,20 @@ set_isdisjoint_impl(PySetObject *so, PyObject *other) while ((key = PyIter_Next(it)) != NULL) { rv = set_contains_key(so, key); + PyRegion_RemoveLocalRef(key); Py_DECREF(key); if (rv < 0) { + PyRegion_RemoveLocalRef(it); Py_DECREF(it); return NULL; } if (rv) { + PyRegion_RemoveLocalRef(it); Py_DECREF(it); Py_RETURN_FALSE; } } + PyRegion_RemoveLocalRef(it); Py_DECREF(it); if (PyErr_Occurred()) return NULL; @@ -1704,20 +1905,32 @@ set_difference_update_internal(PySetObject *so, PyObject *other) if (other == NULL) return -1; } else { + if(PyRegion_AddLocalRef(other)) { + return -1; + } Py_INCREF(other); } while (set_next((PySetObject *)other, &pos, &entry)) { PyObject *key = entry->key; + if(PyRegion_AddLocalRef(key)) { + PyRegion_RemoveLocalRef(other); + Py_DECREF(other); + return -1; + } Py_INCREF(key); + // DONE Migration if (set_discard_entry(so, key, entry->hash) < 0) { + PyRegion_RemoveLocalRef(other); + PyRegion_RemoveLocalRef(key); Py_DECREF(other); Py_DECREF(key); return -1; } + PyRegion_RemoveLocalRef(key); Py_DECREF(key); } - + PyRegion_RemoveLocalRef(other); Py_DECREF(other); } else { PyObject *key, *it; @@ -1727,12 +1940,16 @@ set_difference_update_internal(PySetObject *so, PyObject *other) while ((key = PyIter_Next(it)) != NULL) { if (set_discard_key(so, key) < 0) { + PyRegion_RemoveLocalRef(it); + PyRegion_RemoveLocalRef(key); Py_DECREF(it); Py_DECREF(key); return -1; } + PyRegion_RemoveLocalRef(key); Py_DECREF(key); } + PyRegion_RemoveLocalRef(it); Py_DECREF(it); if (PyErr_Occurred()) return -1; @@ -1781,6 +1998,7 @@ set_copy_and_difference(PySetObject *so, PyObject *other) return NULL; if (set_difference_update_internal((PySetObject *) result, other) == 0) return result; + assert(PyRegion_IsLocal(result)); Py_DECREF(result); return NULL; } @@ -1819,20 +2037,30 @@ set_difference(PySetObject *so, PyObject *other) while (set_next(so, &pos, &entry)) { key = entry->key; hash = entry->hash; + if(PyRegion_AddLocalRef(key)) { + PyRegion_RemoveLocalRef(result); + Py_DECREF(result); + return NULL; + } Py_INCREF(key); rv = _PyDict_Contains_KnownHash(other, key, hash); if (rv < 0) { + PyRegion_RemoveLocalRef(result); + PyRegion_RemoveLocalRef(key); Py_DECREF(result); Py_DECREF(key); return NULL; } if (!rv) { if (set_add_entry((PySetObject *)result, key, hash)) { + PyRegion_RemoveLocalRef(result); + PyRegion_RemoveLocalRef(key); Py_DECREF(result); Py_DECREF(key); return NULL; } } + PyRegion_RemoveLocalRef(key); Py_DECREF(key); } return result; @@ -1842,20 +2070,30 @@ set_difference(PySetObject *so, PyObject *other) while (set_next(so, &pos, &entry)) { key = entry->key; hash = entry->hash; + if(PyRegion_AddLocalRef(key)) { + PyRegion_RemoveLocalRef(result); + Py_DECREF(result); + return NULL; + } Py_INCREF(key); rv = set_contains_entry((PySetObject *)other, key, hash); if (rv < 0) { + PyRegion_RemoveLocalRef(result); + PyRegion_RemoveLocalRef(key); Py_DECREF(result); Py_DECREF(key); return NULL; } if (!rv) { if (set_add_entry((PySetObject *)result, key, hash)) { + PyRegion_RemoveLocalRef(result); + PyRegion_RemoveLocalRef(key); Py_DECREF(result); Py_DECREF(key); return NULL; } } + PyRegion_RemoveLocalRef(key); Py_DECREF(key); } return result; @@ -1895,6 +2133,7 @@ set_difference_multi_impl(PySetObject *so, PyObject * const *others, rv = set_difference_update_internal((PySetObject *)result, other); Py_END_CRITICAL_SECTION(); if (rv) { + assert(PyRegion_IsLocal(result)); Py_DECREF(result); return NULL; } @@ -1930,7 +2169,7 @@ set_isub(PyObject *self, PyObject *other) if (rv < 0) { return NULL; } - return Py_NewRef(so); + return PyRegion_NewRef(so); } static int @@ -1943,18 +2182,24 @@ set_symmetric_difference_update_dict(PySetObject *so, PyObject *other) PyObject *key, *value; Py_hash_t hash; while (_PyDict_Next(other, &pos, &key, &value, &hash)) { + if(PyRegion_AddLocalRef(key)) { + return -1; + } Py_INCREF(key); int rv = set_discard_entry(so, key, hash); if (rv < 0) { + PyRegion_RemoveLocalRef(key); Py_DECREF(key); return -1; } if (rv == DISCARD_NOTFOUND) { if (set_add_entry(so, key, hash)) { + PyRegion_RemoveLocalRef(key); Py_DECREF(key); return -1; } } + PyRegion_RemoveLocalRef(key); Py_DECREF(key); } return 0; @@ -1969,19 +2214,25 @@ set_symmetric_difference_update_set(PySetObject *so, PySetObject *other) Py_ssize_t pos = 0; setentry *entry; while (set_next(other, &pos, &entry)) { - PyObject *key = Py_NewRef(entry->key); + PyObject *key = PyRegion_NewRef(entry->key); + if(key == NULL) { + return -1; + } Py_hash_t hash = entry->hash; int rv = set_discard_entry(so, key, hash); if (rv < 0) { + PyRegion_RemoveLocalRef(key); Py_DECREF(key); return -1; } if (rv == DISCARD_NOTFOUND) { if (set_add_entry(so, key, hash)) { + PyRegion_RemoveLocalRef(key); Py_DECREF(key); return -1; } } + PyRegion_RemoveLocalRef(key); Py_DECREF(key); } return 0; @@ -2026,6 +2277,7 @@ set_symmetric_difference_update_impl(PySetObject *so, PyObject *other) rv = set_symmetric_difference_update_set(so, otherset); Py_END_CRITICAL_SECTION(); + PyRegion_RemoveLocalRef(otherset); Py_DECREF(otherset); } if (rv < 0) { @@ -2053,10 +2305,12 @@ set_symmetric_difference_impl(PySetObject *so, PyObject *other) return NULL; } if (set_update_lock_held(result, other) < 0) { + PyRegion_RemoveLocalRef(result); Py_DECREF(result); return NULL; } if (set_symmetric_difference_update_set(result, so) < 0) { + PyRegion_RemoveLocalRef(result); Py_DECREF(result); return NULL; } @@ -2084,8 +2338,9 @@ set_ixor(PyObject *self, PyObject *other) result = set_symmetric_difference_update((PyObject*)so, other); if (result == NULL) return NULL; + PyRegion_RemoveLocalRef(result); Py_DECREF(result); - return Py_NewRef(so); + return PyRegion_NewRef(so); } /*[clinic input] @@ -2112,6 +2367,7 @@ set_issubset_impl(PySetObject *so, PyObject *other) return NULL; } int result = (PySet_GET_SIZE(tmp) == PySet_GET_SIZE(so)); + PyRegion_RemoveLocalRef(tmp); Py_DECREF(tmp); return PyBool_FromLong(result); } @@ -2120,8 +2376,12 @@ set_issubset_impl(PySetObject *so, PyObject *other) while (set_next(so, &pos, &entry)) { PyObject *key = entry->key; + if(PyRegion_AddLocalRef(key)) { + return NULL; + } Py_INCREF(key); rv = set_contains_entry((PySetObject *)other, key, entry->hash); + PyRegion_RemoveLocalRef(key); Py_DECREF(key); if (rv < 0) { return NULL; @@ -2157,16 +2417,20 @@ set_issuperset_impl(PySetObject *so, PyObject *other) } while ((key = PyIter_Next(it)) != NULL) { int rv = set_contains_key(so, key); + PyRegion_RemoveLocalRef(key); Py_DECREF(key); if (rv < 0) { + PyRegion_RemoveLocalRef(it); Py_DECREF(it); return NULL; } if (!rv) { + PyRegion_AddLocalRef(it); Py_DECREF(it); Py_RETURN_FALSE; } } + PyRegion_RemoveLocalRef(it); Py_DECREF(it); if (PyErr_Occurred()) { return NULL; @@ -2198,6 +2462,7 @@ set_richcompare(PyObject *self, PyObject *w, int op) if (r1 == NULL) return NULL; r2 = PyObject_IsTrue(r1); + PyRegion_RemoveLocalRef(r1); Py_DECREF(r1); if (r2 < 0) return NULL; @@ -2437,6 +2702,9 @@ set___reduce___impl(PySetObject *so) goto done; result = PyTuple_Pack(3, Py_TYPE(so), args, state); done: + assert(PyRegion_IsLocal(args) || args == NULL); + assert(PyRegion_IsLocal(keys) || keys == NULL); + assert(PyRegion_IsLocal(state) || state == NULL); Py_XDECREF(args); Py_XDECREF(keys); Py_XDECREF(state); @@ -2595,14 +2863,14 @@ PyTypeObject PySet_Type = { sizeof(PySetObject), /* tp_basicsize */ 0, /* tp_itemsize */ /* methods */ - set_dealloc, /* tp_dealloc */ + set_dealloc, /* tp_dealloc */ // Done 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ - set_repr, /* tp_repr */ - &set_as_number, /* tp_as_number */ - &set_as_sequence, /* tp_as_sequence */ + set_repr, /* tp_repr */ // Done + &set_as_number, /* tp_as_number */ // Done + &set_as_sequence, /* tp_as_sequence */ // Done 0, /* tp_as_mapping */ PyObject_HashNotImplemented, /* tp_hash */ 0, /* tp_call */ @@ -2614,11 +2882,11 @@ PyTypeObject PySet_Type = { Py_TPFLAGS_BASETYPE | _Py_TPFLAGS_MATCH_SELF, /* tp_flags */ set_doc, /* tp_doc */ - set_traverse, /* tp_traverse */ - set_clear_internal, /* tp_clear */ - set_richcompare, /* tp_richcompare */ + set_traverse, /* tp_traverse */ // Done + set_clear_internal, /* tp_clear */ // Done + set_richcompare, /* tp_richcompare */ // Done offsetof(PySetObject, weakreflist), /* tp_weaklistoffset */ - set_iter, /* tp_iter */ + set_iter, /* tp_iter */ // Done, along with setiter_***. 0, /* tp_iternext */ set_methods, /* tp_methods */ 0, /* tp_members */ @@ -2628,9 +2896,9 @@ PyTypeObject PySet_Type = { 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ - set_init, /* tp_init */ + set_init, /* tp_init */ // Haven't tested yet, but it should be fine. PyType_GenericAlloc, /* tp_alloc */ - set_new, /* tp_new */ + set_new, /* tp_new */ // Done PyObject_GC_Del, /* tp_free */ .tp_vectorcall = set_vectorcall, .tp_version_tag = _Py_TYPE_VERSION_SET, @@ -2692,8 +2960,8 @@ PyTypeObject PyFrozenSet_Type = { 0, /* tp_setattr */ 0, /* tp_as_async */ set_repr, /* tp_repr */ - &frozenset_as_number, /* tp_as_number */ - &set_as_sequence, /* tp_as_sequence */ + &frozenset_as_number, /* tp_as_number */ // Done + &set_as_sequence, /* tp_as_sequence */ // Done 0, /* tp_as_mapping */ frozenset_hash, /* tp_hash */ 0, /* tp_call */ @@ -2702,8 +2970,8 @@ PyTypeObject PyFrozenSet_Type = { 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | - Py_TPFLAGS_BASETYPE | - _Py_TPFLAGS_MATCH_SELF, /* tp_flags */ + Py_TPFLAGS_BASETYPE | + _Py_TPFLAGS_MATCH_SELF, /* tp_flags */ frozenset_doc, /* tp_doc */ set_traverse, /* tp_traverse */ set_clear_internal, /* tp_clear */ diff --git a/Objects/sliceobject.c b/Objects/sliceobject.c index 17d1baaf22be4a..5381c4cc77f40e 100644 --- a/Objects/sliceobject.c +++ b/Objects/sliceobject.c @@ -128,13 +128,17 @@ _PyBuildSlice_Consume2(PyObject *start, PyObject *stop, PyObject *step) } } + PyRegion_TakeRefs(obj, start, stop); obj->start = start; obj->stop = stop; + PyRegion_AddRef(obj, step); obj->step = Py_NewRef(step); _PyObject_GC_TRACK(obj); return obj; error: + PyRegion_RemoveRef(obj, start); + PyRegion_RemoveRef(obj, stop); Py_DECREF(start); Py_DECREF(stop); return NULL; @@ -152,8 +156,8 @@ PySlice_New(PyObject *start, PyObject *stop, PyObject *step) if (stop == NULL) { stop = Py_None; } - return (PyObject *)_PyBuildSlice_Consume2(Py_NewRef(start), - Py_NewRef(stop), step); + return (PyObject *)_PyBuildSlice_Consume2(PyRegion_NewRef(start), + PyRegion_NewRef(stop), step); } PyObject * @@ -348,6 +352,9 @@ slice_dealloc(PyObject *op) { PySliceObject *r = _PySlice_CAST(op); PyObject_GC_UnTrack(r); + PyRegion_RemoveRef(r, r->start); + PyRegion_RemoveRef(r, r->stop); + PyRegion_RemoveRef(r, r->step); Py_DECREF(r->step); Py_DECREF(r->start); Py_DECREF(r->stop); @@ -388,7 +395,10 @@ evaluate_slice_index(PyObject *v) /* Compute slice indices given a slice and length. Return -1 on failure. Used by slice.indices and rangeobject slicing. Assumes that `len` is a - nonnegative instance of PyLong. */ + nonnegative instance of PyLong. + + For now, start_ptr, stop_ptr, step_ptr must be local. +*/ int _PySlice_GetLongIndices(PySliceObject *self, PyObject *length, @@ -396,6 +406,7 @@ _PySlice_GetLongIndices(PySliceObject *self, PyObject *length, PyObject **step_ptr) { PyObject *start=NULL, *stop=NULL, *step=NULL; + PyObject *final_start=NULL, *final_stop=NULL; PyObject *upper=NULL, *lower=NULL; int step_is_negative, cmp_result; @@ -433,12 +444,21 @@ _PySlice_GetLongIndices(PySliceObject *self, PyObject *length, } else { lower = _PyLong_GetZero(); + if(PyRegion_AddLocalRef(length)) { + goto error; + } upper = Py_NewRef(length); } /* Compute start. */ if (self->start == Py_None) { - start = Py_NewRef(step_is_negative ? upper : lower); + final_start = step_is_negative ? upper : lower; + if(PyRegion_AddLocalRef(final_start)) { + goto error; + } + start = Py_NewRef(final_start); + /* Original */ + // start = Py_NewRef(step_is_negative ? upper : lower); } else { start = evaluate_slice_index(self->start); @@ -448,7 +468,10 @@ _PySlice_GetLongIndices(PySliceObject *self, PyObject *length, if (_PyLong_IsNegative((PyLongObject *)start)) { /* start += length */ PyObject *tmp = PyNumber_Add(start, length); - Py_SETREF(start, tmp); + if(PyRegion_XSETLOCALREF(start, tmp)) { + goto error; + } + // Original: Py_SETREF(start, tmp); if (start == NULL) goto error; @@ -456,7 +479,10 @@ _PySlice_GetLongIndices(PySliceObject *self, PyObject *length, if (cmp_result < 0) goto error; if (cmp_result) { - Py_SETREF(start, Py_NewRef(lower)); + if(PyRegion_XSETLOCALNEWREF(start, lower)) { + goto error; + } + // Original: Py_SETREF(start, Py_NewRef(lower)); } } else { @@ -464,14 +490,23 @@ _PySlice_GetLongIndices(PySliceObject *self, PyObject *length, if (cmp_result < 0) goto error; if (cmp_result) { - Py_SETREF(start, Py_NewRef(upper)); + if(PyRegion_XSETLOCALNEWREF(start, upper)) { + goto error; + } + // Original: Py_SETREF(start, Py_NewRef(upper)); } } } /* Compute stop. */ if (self->stop == Py_None) { - stop = Py_NewRef(step_is_negative ? lower : upper); + final_stop = step_is_negative ? lower : upper; + if(PyRegion_AddLocalRef(final_stop)) { + goto error; + } + stop = Py_NewRef(final_stop); + /* Original */ + // stop = Py_NewRef(step_is_negative ? lower : upper); } else { stop = evaluate_slice_index(self->stop); @@ -481,7 +516,10 @@ _PySlice_GetLongIndices(PySliceObject *self, PyObject *length, if (_PyLong_IsNegative((PyLongObject *)stop)) { /* stop += length */ PyObject *tmp = PyNumber_Add(stop, length); - Py_SETREF(stop, tmp); + if(PyRegion_XSETLOCALREF(stop, tmp)) { + goto error; + } + // Original: Py_SETREF(stop, tmp); if (stop == NULL) goto error; @@ -489,7 +527,10 @@ _PySlice_GetLongIndices(PySliceObject *self, PyObject *length, if (cmp_result < 0) goto error; if (cmp_result) { - Py_SETREF(stop, Py_NewRef(lower)); + if(PyRegion_XSETLOCALNEWREF(stop, lower)) { + goto error; + } + // Original: Py_SETREF(stop, Py_NewRef(lower)); } } else { @@ -497,20 +538,30 @@ _PySlice_GetLongIndices(PySliceObject *self, PyObject *length, if (cmp_result < 0) goto error; if (cmp_result) { - Py_SETREF(stop, Py_NewRef(upper)); + if(PyRegion_XSETLOCALNEWREF(stop, upper)) { + goto error; + } + // Original: Py_SETREF(stop, Py_NewRef(upper)); } } } - + // Call TakeRef if the signature is changed. *start_ptr = start; *stop_ptr = stop; *step_ptr = step; + PyRegion_RemoveLocalRef(upper); + PyRegion_RemoveLocalRef(lower); Py_DECREF(upper); Py_DECREF(lower); return 0; error: *start_ptr = *stop_ptr = *step_ptr = NULL; + PyRegion_RemoveLocalRef(upper); + PyRegion_RemoveLocalRef(lower); + PyRegion_RemoveLocalRef(step); + PyRegion_RemoveLocalRef(start); + PyRegion_RemoveLocalRef(stop); Py_XDECREF(start); Py_XDECREF(stop); Py_XDECREF(step); diff --git a/Objects/stringlib/unicode_format.h b/Objects/stringlib/unicode_format.h index ff32db65b11a0b..ff255554493b30 100644 --- a/Objects/stringlib/unicode_format.h +++ b/Objects/stringlib/unicode_format.h @@ -418,6 +418,7 @@ get_field_object(SubString *input, PyObject *args, PyObject *kwargs, } if (kwargs == NULL) { PyErr_SetObject(PyExc_KeyError, key); + assert(PyRegion_IsLocal(key)); Py_DECREF(key); goto error; } @@ -425,6 +426,7 @@ get_field_object(SubString *input, PyObject *args, PyObject *kwargs, code is no longer just used with kwargs. It might be passed a non-dict when called through format_map. */ obj = PyObject_GetItem(kwargs, key); + assert(PyRegion_IsLocal(key)); Py_DECREF(key); if (obj == NULL) { goto error; @@ -474,12 +476,18 @@ get_field_object(SubString *input, PyObject *args, PyObject *kwargs, goto error; /* assign to obj */ - Py_SETREF(obj, tmp); + if(PyRegion_XSETLOCALREF(obj, tmp)) { + PyRegion_RemoveLocalRef(tmp); + Py_DECREF(tmp); + goto error; + } + // Py_SETREF(obj, tmp); } /* end of iterator, this is the non-error case */ if (ok == 1) return obj; error: + PyRegion_RemoveLocalRef(obj); Py_XDECREF(obj); return NULL; } @@ -825,7 +833,12 @@ output_markup(SubString *field_name, SubString *format_spec, goto done; /* do the assignment, transferring ownership: fieldobj = tmp */ - Py_SETREF(fieldobj, tmp); + if(PyRegion_XSETLOCALREF(fieldobj, tmp)) { + PyRegion_RemoveLocalRef(tmp); + Py_DECREF(tmp); + goto done; + } + // Py_SETREF(fieldobj, tmp); tmp = NULL; } @@ -851,6 +864,8 @@ output_markup(SubString *field_name, SubString *format_spec, result = 1; done: + PyRegion_RemoveLocalRef(fieldobj); + PyRegion_RemoveLocalRef(tmp); Py_XDECREF(fieldobj); Py_XDECREF(tmp); diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index f95eb14b288388..4a6d21579d236d 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -1226,7 +1226,7 @@ tuple_iter(PyObject *seq) static inline int maybe_freelist_push(PyTupleObject *op) { - if (!Py_IS_TYPE(op, &PyTuple_Type)) { + if (!Py_IS_TYPE(op, &PyTuple_Type) || !PyRegion_IsLocal(op)) { return 0; } Py_ssize_t index = Py_SIZE(op) - 1; diff --git a/Python/context.c b/Python/context.c index 79e5777b72e861..9a1ddcb5ed5e33 100644 --- a/Python/context.c +++ b/Python/context.c @@ -269,7 +269,9 @@ PyContextVar_New(const char *name, PyObject *def) return (PyObject *)var; } - +/* +Argument: the ContextVar Object, fallback value (or NULL), and output parameter for the value (which is set to NULL on error). +*/ int PyContextVar_Get(PyObject *ovar, PyObject *def, PyObject **val) { diff --git a/clean_old_cpython.sh b/clean_old_cpython.sh new file mode 100644 index 00000000000000..840c1492dd5479 --- /dev/null +++ b/clean_old_cpython.sh @@ -0,0 +1,3 @@ +make distclean # Clears out the old configuration and object files +./configure --with-pydebug --with-assertions CFLAGS='-O0 -g' CXXFLAGS='-O0 -g' +make -j8 diff --git a/test_code/enum_test/create_enum.py b/test_code/enum_test/create_enum.py new file mode 100644 index 00000000000000..e15013523a78cd --- /dev/null +++ b/test_code/enum_test/create_enum.py @@ -0,0 +1,111 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable +from enum import Enum + +class A: pass +freeze(A()) + +r = Region() +input("Press Enter to create objects...") +print(f"Region r: {r}") +r.a = A() +r.b = A() +r.c = A() +r.d = A() +r.e = A() + + +#------------------Problem with LRC should not be increases------------------ +# print(f"Region r: {r}") +# r.arr = [r.a, r.b] +# print(f"Region r after creating arr: {r}") +# # input("Press Enter to create enum...") +# r.it_arr = iter(r.arr) +# print(f"Region r after creating iterator: {r}") +# # input("Press Enter to create enum...") +# obj = enumerate(r.it_arr) # LRC +1 since obj points to r.it_arr +# print(f"Region r after creating enum: {r}") +# input("Press Enter to create enum...") +# x = next(obj) +# print(f"Region r after getting next from enum: {r}") +# input("Press Enter to move enum...") +# r.re2 = x +# print(f"Region r after getting next from enum: {r}") +# r.y = next(obj) +# print(f"Region r after getting next from enum: {r}") +# x = None +# print(f"Region r after deleting re1: {r}") +# r.y = None +# print(f"Region r after deleting re2: {r}") +# obj = None +# print(f"Region r after deleting obj: {r}") + +#------------------Problem with LRC not being decreased------------------ +print(f"Region r: {r}") +r.arr = [r.a, r.b, r.c, r.d] +print(f"Region r after creating arr: {r}") +# input("Press Enter to create enum...") +r.it_arr = iter(r.arr) +print(f"Region r after creating iterator: {r}") +# input("Press Enter to create enum...") +obj = enumerate(r.it_arr) # LRC +1 since obj points to r.it_arr +print(f"Region r after creating enum: {r}") +# input("Press Enter to create enum...") +r.re1 = next(obj) +print(f"{is_local(obj)}") +print(f"Region r after getting next from enum: {r}") +# input("Press Enter to create enum...") +re2 = next(obj) +print(f"Region r after getting next from enum: {r}") +r.re3 = next(obj) +print(f"Region r after getting next from enum: {r}") +re4 = next(obj) +print(f"Region r after getting next from enum: {r}") +r.re1 = None +print(f"Region r after deleting re1: {r}") +re2 = None +print(f"Region r after deleting re2: {r}") +r.re3 = None +print(f"Region r after deleting re3: {r}") +re4 = None +print(f"Region r after deleting re4: {r}") +obj = None +print(f"Region r after deleting obj: {r}") + +#------------------Problem with GC------------------ +# print(f"Region r: {r}") +# r.arr = [r.a, r.b] +# print(f"Region r after creating arr: {r}") +# # input("Press Enter to create enum...") +# r.it_arr = iter(r.arr) +# print(f"Region r after creating iterator: {r}") +# input("Press Enter to create enum...") +# r.obj = enumerate(r.it_arr) # LRC +1 since obj points to r.it_arr +# print(f"Region r after creating enum: {r}") +# input("Press Enter to create enum...") +# re1 = next(r.obj) +# print(f"{is_local(r.obj)}") +# print(f"Region r after getting next from enum: {r}") +# input("Press Enter to create enum...") +# r.re2 = next(r.obj) +# print(f"Region r after getting next from enum: {r}") + + +# print(f"Region r: {r}") +# r.arr = [r.a, r.b, r.c, r.d, r.e] +# print(f"Region r after creating arr: {r}") +# # input("Press Enter to create enum...") +# r.it_arr = iter(r.arr) +# print(f"Region r after creating iterator: {r}") +# # input("Press Enter to create enum...") +# obj = enumerate(r.it_arr) # LRC +1 since obj points to r.it_arr +# print(f"Region r after creating enum: {r}") +# input("Press Enter to create enum...") +# re = next(obj) +# print(f"Region r after getting next from enum: {r}") +# r.re2 = next(obj) +# print(f"Region r after getting next from enum: {r}") +# re3 = next(obj) +# print(f"Region r after getting next from enum: {r}") +# r.re4 = next(obj) +# print(f"Region r after getting next from enum: {r}") \ No newline at end of file diff --git a/test_code/itertool_test/itertool_batch1.py b/test_code/itertool_test/itertool_batch1.py new file mode 100644 index 00000000000000..d1da440b32d893 --- /dev/null +++ b/test_code/itertool_test/itertool_batch1.py @@ -0,0 +1,79 @@ +from itertools import batched +from regions import Region, is_local +from immutable import freeze, register_freezable + +class A: pass +freeze(A()) +freeze(batched) +r = Region() +r.a = A() +r.b = A() +r.c = A() +r.d = A() +r.e = A() +r.f = A() + +# r.arr = [r.a, r.b, r.c, r.d, r.e, r.f] +# print(f"Region r after creating arr: {r}") +# r.it_arr = iter(r.arr) +# print(f"Region r after creating iterator: {r}") +# obj = batched(r.it_arr, 3) +# print(f"Region r after creating batched iterator: {r}") +# x = next(obj) +# print(f"Region r after getting next from batched iterator: {r}") +# print(f"x: {x}") +# y = next(obj) +# print(f"Region r after getting next from batched iterator: {r}") +# print(f"y: {y}") +# x = None +# print(f"Region r after deleting x: {r}") +# y = None +# print(f"Region r after deleting y: {r}") +# obj = None +# print(f"Region r after deleting obj: {r}") + +# r.arr = [r.a, r.b, r.c, r.d, r.e, r.f] +# print(f"Region r after creating arr: {r}") +# r.it_arr = iter(r.arr) +# print(f"Region r after creating iterator: {r}") +# input("Press Enter to create batched iterator...") +# obj = batched(r.it_arr, 3) +# print(f"Region r after creating batched iterator: {r}") +# input("Press Enter to assign batched iterator to r.obj...") +# r.obj = obj +# print(f"Region r after assigning batched iterator to r.obj: {r}") +# r.x = next(r.obj) +# print(f"Region r after getting next from batched iterator: {r}") +# print(f"x: {r.x}") +# y = next(r.obj) +# print(f"Region r after getting next from batched iterator: {r}") +# print(f"y: {y}") +# r.x = None +# print(f"Region r after deleting x: {r}") +# y = None +# print(f"Region r after deleting y: {r}") +# obj = None +# print(f"Region r after deleting obj: {r}") + +r.arr = [r.a, r.b, r.c, r.d, r.e, r.f] +print(f"Region r after creating arr: {r}") +r.it_arr = iter(r.arr) +print(f"Region r after creating iterator: {r}") +input("Press Enter to create batched iterator...") +obj = batched(r.it_arr, 3) +print(f"Region r after creating batched iterator: {r}") +input("Press Enter to assign batched iterator to r.obj...") +r.obj = obj +print(f"Region r after assigning batched iterator to r.obj: {r}") +x = next(r.obj) +print(f"Region r after getting next from batched iterator: {r}") +print(f"x: {x}") +r.y = next(r.obj) +print(f"Region r after getting next from batched iterator: {r}") +print(f"y: {r.y}") +x = None +print(f"Region r after deleting x: {r}") +r.y = None +print(f"Region r after deleting y: {r}") +obj = None +print(f"Region r after deleting obj: {r}") \ No newline at end of file diff --git a/test_code/itertool_test/itertool_pairwise1.py b/test_code/itertool_test/itertool_pairwise1.py new file mode 100644 index 00000000000000..fd89fb3703f5c8 --- /dev/null +++ b/test_code/itertool_test/itertool_pairwise1.py @@ -0,0 +1,92 @@ +from itertools import pairwise +from regions import Region, is_local +from immutable import freeze, register_freezable + +class A: pass +freeze(A()) +freeze(pairwise) +r = Region() +r.a = A() +r.b = A() +r.c = A() +r.d = A() +r.e = A() +r.f = A() + +# r.arr = [r.a, r.b, r.c, r.d, r.e, r.f] +# print(f"Region r after creating arr: {r}") +# r.it_arr = iter(r.arr) +# print(f"Region r after creating iterator: {r}") +# obj = pairwise(r.it_arr) +# print(f"Region r after creating pairwise iterator: {r}") +# input("Press Enter to assign pairwise iterator to r.obj...") +# x = next(obj) # LRC +2 from tuple to 2 elements in the region, and +1 from obj to old (right element of tuple) +# print(f"Region r after getting next from pairwise iterator: {r}") +# print(f"x: {x}") +# y = next(obj) # LRC +2 +# print(f"Region r after getting next from pairwise iterator: {r}") +# print(f"y: {y}") +# r.z = next(obj) # LRC +0 +# print(f"Region r after getting next from pairwise iterator: {r}") +# print(f"z: {r.z}") +# x = None # LRC -2 +# print(f"Region r after deleting x: {r}") +# y = None # LRC -2 +# print(f"Region r after deleting y: {r}") +# r.z = None # LRC -0 +# print(f"Region r after deleting z: {r}") +# obj = None # LRC -2 from deleting ref from obj to r.it and obj to old +# print(f"Region r after deleting obj: {r}") + +# r.arr = [r.a, r.b, r.c, r.d, r.e, r.f] +# print(f"Region r after creating arr: {r}") +# r.it_arr = iter(r.arr) +# print(f"Region r after creating iterator: {r}") +# obj = pairwise(r.it_arr) +# print(f"Region r after creating pairwise iterator: {r}") +# input("Press Enter to assign pairwise iterator to r.obj...") +# x = next(obj) # LRC +3 +# print(f"Region r after getting next from pairwise iterator: {r}") +# print(f"x: {x}") +# x = None +# print(f"Region r after deleting x: {r}") +# x = next(obj) # LRC +0 since everything is already set from the previous x = next(obj) +# print(f"Region r after getting next from pairwise iterator: {r}") +# print(f"x: {x}") +# y = next(obj) # LRC+2 +# print(f"Region r after getting next from pairwise iterator: {r}") +# print(f"y: {y}") +# x = None # LRC -2 +# print(f"Region r after deleting x: {r}") +# y = None # LRC -2 +# print(f"Region r after deleting y: {r}") +# obj = None # LRC -2 from deleting ref from obj to r.it and obj to old +# print(f"Region r after deleting obj: {r}") + +# r.arr = [r.a, r.b, r.c, r.d, r.e, r.f] +# print(f"Region r after creating arr: {r}") +# r.it_arr = iter(r.arr) +# print(f"Region r after creating iterator: {r}") +# # input("Press Enter to create pairwise iterator...") +# obj = pairwise(r.it_arr) # LRC +1 +# print(f"Region r after creating pairwise iterator: {r}") +# # input("Press Enter to assign pairwise iterator to r.obj...") +# r.x = next(obj) # LRC +1 from obj to tuple, and +1 from obj->old to right element of tuple +# print(f"Region r after getting next from pairwise iterator: {r}") +# print(f"x: {r.x}") +# y = next(obj) # LRC +2 bacause of tuple to 2 elements in the region, -1 from deleting ref from obj->result to tuple +# print(f"Region r after getting next from pairwise iterator: {r}") +# print(f"y: {y}") +# r.x = None # LRC -0 +# print(f"Region r after deleting x: {r}") +# y = None # LRC -2 +# print(f"Region r after deleting y: {r}") +# obj = None # LRC -2 from deleting ref from obj to r.it and -1 from obj to old +# print(f"Region r after deleting obj: {r}") + +r.arr = [r.a, r.b, r.c, r.d, r.e, r.f] +print(f"Region r after creating arr: {r}") +r.it_arr = iter(r.arr) +print(f"Region r after creating iterator: {r}") +r.obj = pairwise(r.it_arr) +print(f"Region r after creating pairwise iterator: {r}") \ No newline at end of file diff --git a/test_code/range_test/longrangeiter_1.py b/test_code/range_test/longrangeiter_1.py new file mode 100644 index 00000000000000..0ca4846c7ca780 --- /dev/null +++ b/test_code/range_test/longrangeiter_1.py @@ -0,0 +1,27 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable +import sys + +r1 = Region() +# input("0: Press Enter to continue...") +r1.start = 2**100 +r1.stop = 2**200 +r1.step = 2**50 +print(f"{sys.getrefcount(r1.start)}") +print(f"Initial Region: {r1}") #_lrc=3 +input("1: Press Enter to continue...") +r_large = range(r1.start, r1.stop, r1.step) +print(f"{r_large}") +print(f"{r1.owns(r_large)}") +print(f"Region 1 after setting start: {r1}") #_lrc=6 +# input("2: Press Enter to continue...") +r1.range = r_large +print(f"Region 1 after moving range into the region: {r1}") #_lrc=4: -3 from subtracting start, stop, and end to the region since the range obj is moved into the region, +1 from r_large +print(f"{r1.owns(r_large)}") +input("3: Press Enter to continue...") +iter_range = iter(r_large) # Create "iter" object from "range" +print(f"Region 1 after creating iter: {r1}") # _lrc=6 since it borrows r.start and r.step +iter_range = None +print(f"Region 1 after deleting iter: {r1}") #_lrc=4 +r_large = None +print(f"Region 1 after deleting r_large: {r1}") #_lrc=3 \ No newline at end of file diff --git a/test_code/range_test/longrangeiter_2.py b/test_code/range_test/longrangeiter_2.py new file mode 100644 index 00000000000000..1d7ee9392ee2b7 --- /dev/null +++ b/test_code/range_test/longrangeiter_2.py @@ -0,0 +1,30 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable +import sys + +r1 = Region() +# input("0: Press Enter to continue...") +r1.start = 2**100 +r1.stop = 2**200 +r1.step = 2**50 +print(f"{sys.getrefcount(r1.start)}") +print(f"Initial Region: {r1}") #_lrc=3 +# input("1: Press Enter to continue...") +r_large = range(r1.start, r1.stop, r1.step) +print(f"{r_large}") +print(f"{r1.owns(r_large)}") +print(f"Region 1 after setting start: {r1}") #_lrc=6 +# input("2: Press Enter to continue...") +r1.range = r_large +print(f"Region 1 after moving range into the region: {r1}") #_lrc=4: -3 from subtracting start, stop, and end to the region since the range obj is moved into the region, +1 from r_large +print(f"{r1.owns(r_large)}") +input("3: Press Enter to continue...") +reversed_range = reversed(r_large) # Create "iter" object from "range" +print(f"{reversed_range}") +# print(f"{hex(id(reversed_range)), hex(id(r_large))}") # reversed_range is not the same object as r_large. +print(f"Region 1 after creating reversed_range: {r1}") +input("continue") +reversed_range = None +print(f"Region 1 after deleting reversed_range: {r1}") +r_large = None +print(f"Region 1 after deleting r_large: {r1}") \ No newline at end of file diff --git a/test_code/range_test/range_argumentIndex.py b/test_code/range_test/range_argumentIndex.py new file mode 100644 index 00000000000000..f9f8341d32dc6c --- /dev/null +++ b/test_code/range_test/range_argumentIndex.py @@ -0,0 +1,42 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable +import sys + +r1 = Region() +# freeze(10) +r1.start = 2**100 +r1.stop = 2**200 +r1.step = 1 +# r1.start = 1 +# r2.stop = 10 +# r3.step = 2 + +# print(f"{r1.owns(r1.start)}") + +print(f"Initial Region: {r1}") + +#print(sys.getrefcount(r.stop)) +# print(f"{r.stop}") +ra1 = range(r1.start, r1.stop, r1.step) +print(f"Region after creating range: {r1}") +print(f"{ra1}") +print(f"r1.owns(ra1): {r1.owns(ra1)}") + +r1.idx1 = 2**105 +idx2 = 2**80 +input("Continue") + +res = ra1[r1.idx1] +print(f"Region after accessing ra1[r1.idx1]: {r1}") +# r1.result_from_idx1 = res +# print(f"Region after setting result_from_idx1: {r1}") + +# res = ra1[idx2] +# print(f"Region after accessing ra1[idx2]: {r1}") # I guess PyRegion_NewRef does nothing in case that idx2 is not in the region. + +res = None +print(f"Region after deleting res: {r1}") + +# print(f"is_local(ra1[0]): {is_local(ra1[0])}") +# print(f"is_local(ra1[1]): {is_local(ra1[1])}") +# print(f"is_local(ra1[2**75]): {is_local(ra1[2**75])}") \ No newline at end of file diff --git a/test_code/range_test/range_argumentSlicing.py b/test_code/range_test/range_argumentSlicing.py new file mode 100644 index 00000000000000..4e067e026c0222 --- /dev/null +++ b/test_code/range_test/range_argumentSlicing.py @@ -0,0 +1,38 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable +import sys + +r1 = Region() +r1.start = 2**10000 +r1.stop = 2**20000 +r1.step = 2**100 + +print(f"Initial Region: {r1}") + +ra1 = range(r1.start, r1.stop, r1.step) +print(f"Region after creating range: {r1}") +# print(f"{ra1}") +print(f"r1.owns(ra1): {r1.owns(ra1)}") + +r1.slicing_start = 2**200 +r1.slicing_stop = 2**300 +r1.slicing_step = 2**100 +# Has a problem when r1.slicing_step is set to 2**40 +input("Continue") + +# res = ra1[r1.slicing_start:r1.slicing_stop +res = ra1[r1.slicing_start:r1.slicing_stop:r1.slicing_step] +print(f"Region after accessing ra1[:r1.slicing_stop]: {r1}") # LRC should not change since the slice object is destroyed. +# print(f"Result of slicing: {res[2]}") +# r1.result_from_idx1 = res +# print(f"Region after setting result_from_idx1: {r1}") + +test_slic_obj = slice(r1.slicing_start, r1.slicing_stop, r1.slicing_step) +print(f"Region after creating test_slic_obj: {r1}") # LRC +3 since the slice object is still alive. + +# res = None +# print(f"Region after deleting res: {r1}") + +# print(f"is_local(ra1[0]): {is_local(ra1[0])}") +# print(f"is_local(ra1[1]): {is_local(ra1[1])}") +# print(f"is_local(ra1[2**75]): {is_local(ra1[2**75])}") diff --git a/test_code/range_test/range_test.py b/test_code/range_test/range_test.py new file mode 100644 index 00000000000000..cb0d908013f861 --- /dev/null +++ b/test_code/range_test/range_test.py @@ -0,0 +1,3 @@ +ra = range(1,100,2) +input("Continue") +ra_iter = iter(ra) \ No newline at end of file diff --git a/test_code/range_test/range_testHash.py b/test_code/range_test/range_testHash.py new file mode 100644 index 00000000000000..7d71fbf7492cdf --- /dev/null +++ b/test_code/range_test/range_testHash.py @@ -0,0 +1,22 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable +import sys + +r1 = Region() +r1.start = 2**1000 +r1.stop = 2**2000 +r1.step = 2**100 + +print(f"Initial Region: {r1}") + +ra1 = range(r1.start, r1.stop, r1.step) +print(f"Region after creating range: {r1}") +# print(f"{ra1}") +print(f"r1.owns(ra1): {r1.owns(ra1)}") + +input("Continue") +h = hash(ra1) +print(f"Region after hashing ra1: {r1}") +print(f"Hash of ra1: {h}") +print(f"{r1.owns(ra1)}") +# print(f"{ra1}") \ No newline at end of file diff --git a/test_code/range_test/range_threeArgs.py b/test_code/range_test/range_threeArgs.py new file mode 100644 index 00000000000000..43222dd423d379 --- /dev/null +++ b/test_code/range_test/range_threeArgs.py @@ -0,0 +1,36 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable +import sys + +r1 = Region() +r2 = Region() +r3 = Region() +# freeze(10) +r1.start = 445435435435535340000000 +r2.stop = 445435435435535345540000 +r3.step = 4454354354355500000 +# r1.start = 1 +# r2.stop = 10 +# r3.step = 2 +print("sys.getrefcount(r1.start): ", sys.getrefcount(r1.start)) +print("sys.getrefcount(r2.stop): ", sys.getrefcount(r2.stop)) +print("sys.getrefcount(r3.step): ", sys.getrefcount(r3.step)) +# print(f"{r1.owns(r1.start)}") +# print(f"{r2.owns(r2.stop)}") +# print(f"{r3.owns(r3.step)}") + +print(f"Initial Region: {r1}") +print(f"Initial Region: {r2}") +print(f"Initial Region: {r3}") +#print(sys.getrefcount(r.stop)) +# print(f"{r.stop}") +input("Continue") +ra = range(r1.start, r2.stop, r3.step) +print(f"{ra}") +print(f"Region 1 after setting stop: {r1}") +print(f"Region 2 after setting stop: {r2}") +print(f"Region 3 after setting step: {r3}") +ra = None +print(f"Region 1 after deleting ra: {r1}") +print(f"Region 2 after deleting ra: {r2}") +print(f"Region 3 after deleting ra: {r3}") \ No newline at end of file diff --git a/test_code/range_test/rangeiter_vs_longrangeiter.py b/test_code/range_test/rangeiter_vs_longrangeiter.py new file mode 100644 index 00000000000000..66a3558ac3f221 --- /dev/null +++ b/test_code/range_test/rangeiter_vs_longrangeiter.py @@ -0,0 +1,11 @@ +# Small range - fits in C long +r_small = range(1, 100, 2) +it_small = iter(r_small) +print(type(r_small)) # +print(type(it_small)) # <- PyRangeIter_Type + +# Large range - values don't fit in C long +r_large = range(2**100, 2**200, 2**50) +it_large = iter(r_large) +print(type(r_large)) # <- still PyRange_Type! +print(type(it_large)) # <- PyLongRangeIter_Type \ No newline at end of file diff --git a/test_code/report_code/closed_region.py b/test_code/report_code/closed_region.py new file mode 100644 index 00000000000000..b46c68e7c3bc1b --- /dev/null +++ b/test_code/report_code/closed_region.py @@ -0,0 +1,11 @@ +from regions import Region, is_local, Cown + +r = Region() +obj = {"key": "value"} +r.start = obj +c = Cown(r) +c.acquire() +print(f"{c.value.start['key']}") +r = None +obj = None +c.release() \ No newline at end of file diff --git a/test_code/report_code/cross_region.py b/test_code/report_code/cross_region.py new file mode 100644 index 00000000000000..83f64a36aa536a --- /dev/null +++ b/test_code/report_code/cross_region.py @@ -0,0 +1,11 @@ +from regions import Region, is_local + +r1 = Region() +r2 = Region() +r3 = Region() +r2.obj = {"key": "value"} + +r1.a = r2 +# r1.a = r2.obj # RuntimeError: References to objects in other regions are forbidden +# r1.b = r2 # RuntimeError: Regions are not allowed to have multiple parents +# r3.c = r2 # RuntimeError: Regions are not allowed to have multiple parents \ No newline at end of file diff --git a/test_code/report_code/freeze_item.py b/test_code/report_code/freeze_item.py new file mode 100644 index 00000000000000..6762d86ddf08f0 --- /dev/null +++ b/test_code/report_code/freeze_item.py @@ -0,0 +1,19 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable, isfrozen + +r = Region() +class A: pass +obj = {"key": "value"} +print(f"Initial Region: {r}") +r.start = obj +print(f"Region after setting start: {r}") +print(f"Obj is in the local region: {is_local(obj)}") +print(f"Region owns obj: {r.owns(obj)}") +print(f"{isfrozen(obj)}") +print(f"{isfrozen(obj['key'])}") +freeze(obj) +print(f"Region after freezing obj: {r}") +print(f"Obj is in the local region: {is_local(obj)}") +print(f"Region owns obj: {r.owns(obj)}") +print(f"{isfrozen(obj)}") +print(f"{isfrozen(obj['key'])}") \ No newline at end of file diff --git a/test_code/report_code/move_from_localR_to_R.py b/test_code/report_code/move_from_localR_to_R.py new file mode 100644 index 00000000000000..da420c8f7cfe9f --- /dev/null +++ b/test_code/report_code/move_from_localR_to_R.py @@ -0,0 +1,18 @@ +from regions import Region, is_local + +r = Region() +child_obj = {"child_key": "child_value"} +obj = {"key": "value", "child": child_obj} +print(f"Initial Region: {r}") +print(f"Obj is in the local region: {is_local(obj)}") # True +print(f"Child obj is in the local region: {is_local(child_obj)}") # True +r.start = obj +print(f"Region after setting start: {r}") +print(f"Obj is in the local region: {is_local(obj)}") # False +print(f"Child obj is in the local region: {is_local(child_obj)}") # False +print(f"Region owns obj: {r.owns(obj)}") # True +print(f"Region owns child obj: {r.owns(child_obj)}") # True + +obj["key"] = "new_value" +print(f"obj after modification: {obj}") +print(f"Region after modifying obj: {r}") \ No newline at end of file diff --git a/test_code/report_code/onlygil.py b/test_code/report_code/onlygil.py new file mode 100644 index 00000000000000..b334d699301b3e --- /dev/null +++ b/test_code/report_code/onlygil.py @@ -0,0 +1,33 @@ +import time +import threading +import _interpreters # Python 3.12+ +import sys + +print(sys.version) + +def cpu_task(): + total = 0 + for i in range(2_000_000): + total += i ** 2 + return total + +# --- METHOD A: threading (your current approach) --- +start = time.perf_counter() +t1 = threading.Thread(target=cpu_task) +t2 = threading.Thread(target=cpu_task) +t1.start(); t2.start() +t1.join(); t2.join() +thread_time = time.perf_counter() - start + +# --- METHOD B: sequential (baseline) --- +start = time.perf_counter() +cpu_task() +cpu_task() +sequential_time = time.perf_counter() - start + +print(f"Sequential : {sequential_time:.3f}s") +print(f"Threading : {thread_time:.3f}s") +print(f"Speedup : {sequential_time / thread_time:.2f}x") +print() +print("If speedup ≈ 1.0 → threads gave NO parallelism benefit (GIL bottleneck)") +print("If speedup ≈ 2.0 → true parallelism achieved") diff --git a/test_code/report_code/race_cond.py b/test_code/report_code/race_cond.py new file mode 100644 index 00000000000000..b6777a100297b8 --- /dev/null +++ b/test_code/report_code/race_cond.py @@ -0,0 +1,72 @@ +from regions import Region, is_local, Cown +import threading + +r = Region() +# s_arr = np.empty(3, dtype=object) # Array that holds Python objects +s_arr = list(range(3)) # Using a list instead of numpy array to avoid complications with numpy's internal optimizations +r.arr = s_arr +c = Cown(r) +# ask for the diagram drawing again from Fred +print(f"r.owns(s_arr): {r.owns(s_arr)}") # True +# Create a local Python object +local_dict = {"key": "value", "count": 0} + +# Assign it to the array +s_arr[0] = local_dict # Internally writes PyObject* into data buffer + +# WITHOUT BARRIER: +# Problem: local_dict is still local, but now referenced from arr inside region r +print(f"s_arr in region: {r.owns(s_arr)}") # True +print(f"local_dict in region: {r.owns(local_dict)}") # False - still local +print(f"local_dict is local: {is_local(local_dict)}") # True - didn't transfer! - PROBLEM! + +print(f"Region r: {str(r)}") + +# DANGER: Array contains reference to local object +# If another thread acquires Cown and modifies arr[0], +# but we still have local_dict reference - DATA RACE! + +# c = Cown(r) + +# print(f"{c.value.arr[0]}") +# c.release() + +print("Starting threads that will cause race condition despite using Cown...") + +def race_condition_despite_using_cown(): + c.acquire() + r = c.value + for _ in range(5000): + # print(f"c.owned: {c.owned()}, c.owned_by_thread(); {c.owned_by_thread()}") + # 1. READ + val = r.arr[0]["count"] # work because val takes the copy of the PythonObject of number. + # if "val" is an actual object, val=None is also required because it still makes the region opens (like r). + + # 2. DELAY (This forces the other thread to read the same old 'val') + # Even a tiny computation creates the window for a race condition + _ = [i**2 for i in range(10)] + + # 3. WRITE + r.arr[0]["count"] = val + 1 + r = None + c.release() + +# Launch two threads to increment the same spot +t1 = threading.Thread(target=race_condition_despite_using_cown) +t2 = threading.Thread(target=race_condition_despite_using_cown) + +t1.start() +t2.start() + +r = None +s_arr = None +c.release() + +t1.join() +t2.join() + +c.acquire() #needed to print the data in the print statements, otherwise c.value is not accessible + +print(f"Result: {c.value.arr[0]['count']}") +print(f"Expected: 10000") +print(f"Difference: {10000 - c.value.arr[0]['count']} (If this is > 0, you found the race!)") \ No newline at end of file diff --git a/test_code/report_code/share_nothing.py b/test_code/report_code/share_nothing.py new file mode 100644 index 00000000000000..18b787aac49101 --- /dev/null +++ b/test_code/report_code/share_nothing.py @@ -0,0 +1,22 @@ +from concurrent import interpreters + +# Create a subinterpreter +interp = interpreters.create() + +my_list = [1, 2, 3] + +# Subinterpreter has NO access to main's objects +interp.exec(""" +try: + print(my_list) +except NameError as e: + print(f"NameError: {e}") +""") + +# Share by COPYING the data +interp.exec(f""" +copied_list = {my_list} # A copy, not a reference +print(f"Received a copy: {{copied_list}}") +""") + +interp.close() \ No newline at end of file diff --git a/test_code/report_code/test.py b/test_code/report_code/test.py new file mode 100644 index 00000000000000..8362d24c9094fb --- /dev/null +++ b/test_code/report_code/test.py @@ -0,0 +1,19 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable + +class A: pass +freeze(A()) + +r = Region() +print(f"Initial Region: {r}") +r.a = A() +r.b = A() +r.c = A() +r.d = A() + +arr = [r.a, r.b, r.c, r.d] +print(f"Region r after creating arr: {r}") +it_arr = iter(arr) +print(f"Region r after creating iterator: {r}") +r.it_arr = it_arr +print(f"Region r after assigning iterator to region: {r}") \ No newline at end of file diff --git a/test_code/set_test/add_set_to_region.py b/test_code/set_test/add_set_to_region.py new file mode 100644 index 00000000000000..d5d91addfcb844 --- /dev/null +++ b/test_code/set_test/add_set_to_region.py @@ -0,0 +1,18 @@ +from regions import Region +from immutable import freeze + +class A: pass +freeze(A()) + +r = Region() +r.a = A() +r.b = A() +r.c = A() + +print(f"Initial Region: {r}") +input("Press Enter to create set from region attributes...") +arr = set([r.a, r.b, r.c]) +print(f"Region after creating set: {r}") +input("Press Enter to set arr to region...") +r.arr = arr +print(f"Region after setting arr to set: {r}") \ No newline at end of file diff --git a/test_code/set_test/frozenset_create_delete_1.py b/test_code/set_test/frozenset_create_delete_1.py new file mode 100644 index 00000000000000..ed173148fb9698 --- /dev/null +++ b/test_code/set_test/frozenset_create_delete_1.py @@ -0,0 +1,29 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable + +r = Region() +print(f"Initial Region: {r}") +class A: pass +freeze(A()) + +r.a = A() +r.b = A() +r.c = A() +r.d = A() +r.e = A() +r.f = A() + +r.arr1 = [r.a, r.b, r.c] +print(f"Region after setting arr1: {r}") +s1 = frozenset(r.arr1) +print(f"Region after creating frozenset s1: {r}") +r.set1 = s1 +print(f"Region after setting set1: {r}") + +# s2 = frozenset(s1) +# print(f"Region after creating frozenset s2: {r}") +# print(f"{s2 is r.set1}") + +s2 = s1.copy() +print(f"Region after copying s1 to s2: {r}") +print(f"{s2 is r.set1}") \ No newline at end of file diff --git a/test_code/set_test/set_itertestprint.py b/test_code/set_test/set_itertestprint.py new file mode 100644 index 00000000000000..99a1470c5f4bf0 --- /dev/null +++ b/test_code/set_test/set_itertestprint.py @@ -0,0 +1,23 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable + +r = Region() +print(f"Initial Region: {r}") +class A: pass +freeze(A()) + +r.word=A() +r.word2=A() +r.word3=A() +r.word4=A() + +r.arr = [r.word, r.word2, r.word3, r.word4] +print(f"Before for loop: {r}") +s = set(r.arr) +print(f"Before for loop: {r}") + +for i in r.arr: + print(hex(id(i))) +i = None + +print(f"After for loop: {r}") \ No newline at end of file diff --git a/test_code/set_test/setand_test1.py b/test_code/set_test/setand_test1.py new file mode 100644 index 00000000000000..720f42452a07b9 --- /dev/null +++ b/test_code/set_test/setand_test1.py @@ -0,0 +1,93 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable + +r1 = Region() +r2 = Region() +r3 = Region() +r4 = Region() + + +class A: pass; +freeze(A()) + +r1.a = A() +r1.b = A() +r1.c = A() +r1.d = A() +r1.e = A() +r1.f = A() +g = A() +r1.arr1 = [r1.a, r1.b, r1.c] +r1.arr2 = [r1.b, r1.c, r1.f] +r1.arr3 = [r1.b, r1.d, r1.e] + +# print(f"Initial Region: {r}") +# s1 = set(r.arr1) +# print(f"Region after creating set1: {r}") +# print(f"{s1}") +# s2 = set(r.arr2) +# print(f"Region after creating set2: {r}") +# print(f"{s2}") +# input("Press Enter to create set intersection...") +# # result = s1 & s2 +# result = s1.intersection(s2) +# print(f"Region after creating set intersection: {r}") +# print(f"Intersection result: {result}") + +print(f"Initial Region: {r1}") +r1.s1 = set(r1.arr1) +print(f"Region after creating set1: {r1}") +print(f"{r1.s1}") +s2 = set(r1.arr2) +print(f"Region after creating set2: {r1}") +print(f"{s2}") +input("Press Enter to create set intersection...") +# result = s1 & s2 +result = r1.s1.intersection(s2) +print(f"Region after creating set intersection: {r1}") +print(f"Intersection result: {result}") +result = g +print(f"Region after changing result: {r1}") + +# print(f"Initial Region: {r1}") +# s1 = set(r1.arr1) +# print(f"Region after creating set1: {r1}") +# print(f"{s1}") +# s2 = set(r1.arr2) +# print(f"Region after creating set2: {r1}") +# print(f"{s2}") +# s3 = set(r1.arr3) +# print(f"Region after creating set3: {r1}") +# print(f"{s3}") +# input("Press Enter to create set intersection...") +# result = s1.intersection(s2, s3) +# print(f"Region after creating set intersection: {r1}") +# print(f"Intersection result: {result}") + +# print(f"Initial Region: {r1}") +# r1.s1 = set(r1.arr1) +# print(f"Region after creating set1: {r1}") +# print(f"{r1.s1}") +# s2 = set(r1.arr2) +# print(f"Region after creating set2: {r1}") +# print(f"{s2}") +# r1.s3 = set(r1.arr3) +# print(f"Region after creating set3: {r1}") +# print(f"{r1.s3}") +# input("Press Enter to create set intersection...") +# result = r1.s1.intersection(s2, r1.s3) +# print(f"Region after creating set intersection: {r1}") +# print(f"Intersection result: {result}") +# result = g +# print(f"Region after changing result: {r1}") + +# print(f"Initial Region: {r1}") +# print(f"Region r2: {r2}") +# print(f"Region r3: {r3}") +# print(f"Region r4: {r4}\n") +# r1.set1 = set([r2, r3]) +# print(f"Region after creating set1: {r1}") +# print(f"Region r2: {r2}") +# print(f"Region r3: {r3}") +# print(f"Region r4: {r4}\n") +# r1.set2 = set([r2, r4]) \ No newline at end of file diff --git a/test_code/set_test/setcopy_test1.py b/test_code/set_test/setcopy_test1.py new file mode 100644 index 00000000000000..40945385bee574 --- /dev/null +++ b/test_code/set_test/setcopy_test1.py @@ -0,0 +1,21 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable + +r = Region() +print(f"Initial Region: {r}") +class A: pass +freeze(A()) + +r.a = A() +r.b = A() +r.c = A() +r.d = A() +r.e = A() +r.f = A() + +r.arr1 = [r.a, r.b, r.c] +print(f"Region after setting arr1: {r}") +s1 = set(r.arr1) +print(f"Region after creating set s1: {r}") +s2 = s1.copy() +print(f"Region after copying s1 to s2: {r}") diff --git a/test_code/set_test/setiand_test1.py b/test_code/set_test/setiand_test1.py new file mode 100644 index 00000000000000..077d0c9df481f9 --- /dev/null +++ b/test_code/set_test/setiand_test1.py @@ -0,0 +1,93 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable + +r = Region() +r2 = Region() +print(f"Initial Region: {r}") + +class A: pass; +freeze(A()) + +r.a = A() +r.b = A() +r.c = A() +r.d = A() +r.e = A() +r.f = A() + +# r.arr1 = [r.a, r.b, r.c] +# r.arr2 = [r.b, r.c, r.f] +# s1 = set(r.arr1) +# print(f"Region after creating set1: {r}") # +3 +# print(f"{s1}") +# s2 = set(r.arr2) +# print(f"Region after creating set2: {r}") # +3 +# print(f"{s2}") +# input("Press Enter to create set intersection...") +# s1 &= s2 +# # s1.intersection_update(s2) +# print(f"Region after creating set intersection: {r}") # -3: eliminate all points from s1, then +2: add the reference from s1 to the result +# print(f"Intersection result: {s1}") + +# r.arr1 = [r.a, r.b, r.c] +# r.arr2 = [r.f] +# s1 = set(r.arr1) +# print(f"Region after creating set1: {r}") # +3 +# print(f"{s1}") +# s2 = set(r.arr2) +# print(f"Region after creating set2: {r}") # +3 +# print(f"{s2}") +# input("Press Enter to create set intersection...") +# s1 &= s2 +# # s1.intersection_update(s2) +# print(f"Region after creating set intersection: {r}") # -3: eliminate all points from s1 +# print(f"Intersection result: {s1}") + +r.arr1 = [r.a, r.b, r.c, r.d, r.e] +arr2 = [r.a, r.b, r.c, r.f] +print(f"Region after creating arr1 and arr2: {r}") # +3 +r.s1 = set(r.arr1) +print(f"Region after creating set1: {r}") # +0 +print(f"{r.s1}") +input("Press Enter to create set1...") +s2 = set(arr2) +print(f"Region after creating set2: {r}") # +3 +print(f"{s2}") +input("Press Enter to create set intersection...") +r.s1 &= s2 +# r.s1.intersection_update(s2) +print(f"Region after creating set intersection: {r}") +print(f"Intersection result: {r.s1}") + +# r.arr1 = [r.a, r.b, r.c] +# arr2 = [r.a, r.b, r.f] +# r.arr3 = [r.a] +# print(f"Region after creating arr1 and arr2: {r}") # +3 +# r.s1 = set(r.arr1) +# print(f"Region after creating set1: {r}") # +0 +# print(f"{r.s1}") +# s2 = set(arr2) +# print(f"Region after creating set2: {r}") # +3 +# print(f"{s2}") +# r.s3 = set(r.arr3) +# print(f"Region after creating set3: {r}") # +0 +# print(f"{r.s3}") +# input("Press Enter to create set intersection...") +# r.s1.intersection_update(s2, r.s3) +# print(f"Region after creating set intersection: {r}") # Wrong: LRC should not be changed since s1 is in the region +# print(f"Intersection result: {r.s1}") + +# arr1 = [r.a, r.b, r.c] +# r.arr2 = [r.a, r.b, r.f] +# print(f"Region after creating arr1 and arr2: {r}") # +3 +# s1 = set(arr1) +# print(f"Region after creating set1: {r}") # +3 +# print(f"{s1}") +# r.s2 = set(r.arr2) +# print(f"Region after creating set2: {r}") # +0 +# print(f"{r.s2}") +# input("Press Enter to create set intersection...") +# # s1 &= r.s2 +# s1.intersection_update(r.s2) +# print(f"Region after creating set intersection: {r}") +# print(f"Intersection result: {s1}") \ No newline at end of file diff --git a/test_code/set_test/setior_test1.py b/test_code/set_test/setior_test1.py new file mode 100644 index 00000000000000..a44cd75abc68f9 --- /dev/null +++ b/test_code/set_test/setior_test1.py @@ -0,0 +1,53 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable + +r = Region() +r2 = Region() +print(f"Initial Region: {r}") + +class A: pass; +freeze(A()) + +r.a = A() +r.b = A() +r.c = A() +r.d = A() +r.e = A() +r.f = A() +r.arr1 = [r.a, r.b, r.c, r.d] +r.arr2 = [r.b, r.c, r.f, r.e] + +# s1 = set(r.arr1) +# print(f"Region after creating set1: {r}") +# print(f"{s1}") +# s2 = set(r.arr2) +# print(f"Region after creating set2: {r}") +# print(f"{s2}") +# input("Press Enter to create set or...") +# s1 |= s2 +# # s1.update(s2) +# print(f"Region after creating set or: {r}") +# print(f"Or result: {s1}") + +r.s1 = set(r.arr1) +print(f"Region after creating set1: {r}") +# print(f"{r.s1}") +s2 = set(r.arr2) +print(f"Region after creating set2: {r}") +# print(f"{s2}") +input("Press Enter to create set or...") +r.s1 |= s2 +# r.s1.update(s2) +print(f"Region after creating set or: {r}") +print(f"Or result: {r.s1}") + +# r1 = Region() +# r1.a = {1, 2} +# r2 = Region() +# r2.b = {2, 3} + +# print(f"r1: {r1}") +# print(f"r2: {r2}") +# r1.a |= r2.b +# print(f"r1: {r1}") +# print(f"r2: {r2}") \ No newline at end of file diff --git a/test_code/set_test/setissubset_test1.py b/test_code/set_test/setissubset_test1.py new file mode 100644 index 00000000000000..38f78816ee8704 --- /dev/null +++ b/test_code/set_test/setissubset_test1.py @@ -0,0 +1,57 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable + +r = Region() +r2 = Region() +print(f"Initial Region: {r}") + +class A: pass; +freeze(A()) + +r.a = A() +r.b = A() +r.c = A() +r.d = A() +r.e = A() +r.f = A() +r.arr1 = [r.a] +r.arr2 = [r.a, r.b, r.c] +r.arr3 = [r.a, r.b, r.c, r.d, r.e, r.f] +r.arr4 = [r.d] +s1 = set(r.arr1) +print(f"Region after creating set1: {r}") +print(f"{s1}") +s2 = set(r.arr2) +print(f"Region after creating set2: {r}") +print(f"{s2}") +input("Press Enter to issubset...") +# result = s1 <= s2 +result = s1.issubset(s2) +print(f"Region after creating set issubset: {r}") +print(f"IsSubset result: {result}") + +# r.a = A() +# r.b = A() +# r.c = A() +# r2.d = A() +# r2.e = A() +# r2.f = A() +# r2.g = A() +# arr1 = [r.a, r.b, r.c, r2.d] +# arr2 = [r.c, r2.d, r2.e, r2.f, r2.g] +# print(f"Region1 before creating sets: {r}") +# print(f"Region2 before creating sets: {r2}") +# s1 = set(arr1) +# print(f"Region1 after creating set1: {r}") +# print(f"Region2 after creating set1: {r2}") +# print(f"{s1}") +# s2 = set(arr2) +# print(f"Region1 after creating set2: {r}") +# print(f"Region2 after creating set2: {r2}") +# print(f"{s2}") +# input("Press Enter to create set xor...") +# result = s1 ^ s2 +# # result = s1.symmetric_difference(s2) +# print(f"Region1 after creating set xor: {r}") +# print(f"Region2 after creating set xor: {r2}") +# print(f"Xor result: {result}") \ No newline at end of file diff --git a/test_code/set_test/setissuperset_test1.py b/test_code/set_test/setissuperset_test1.py new file mode 100644 index 00000000000000..9a9aa334f36cbe --- /dev/null +++ b/test_code/set_test/setissuperset_test1.py @@ -0,0 +1,57 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable + +r = Region() +r2 = Region() +print(f"Initial Region: {r}") + +class A: pass; +freeze(A()) + +r.a = A() +r.b = A() +r.c = A() +r.d = A() +r.e = A() +r.f = A() +r.arr1 = [r.a] +r.arr2 = [r.a, r.b, r.c] +r.arr3 = [r.a, r.b, r.c, r.d, r.e, r.f] +r.arr4 = [r.d] +s1 = set(r.arr1) +print(f"Region after creating set1: {r}") +print(f"{s1}") +s2 = set(r.arr1) +print(f"Region after creating set2: {r}") +print(f"{s2}") +input("Press Enter to issuperset...") +result = s2 > s1 +# result = s2.issuperset(s1) +print(f"Region after creating set issuperset: {r}") +print(f"IsSuperset result: {result}") + +# r.a = A() +# r.b = A() +# r.c = A() +# r2.d = A() +# r2.e = A() +# r2.f = A() +# r2.g = A() +# arr1 = [r.a, r.b, r.c, r2.d] +# arr2 = [r.c, r2.d, r2.e, r2.f, r2.g] +# print(f"Region1 before creating sets: {r}") +# print(f"Region2 before creating sets: {r2}") +# s1 = set(arr1) +# print(f"Region1 after creating set1: {r}") +# print(f"Region2 after creating set1: {r2}") +# print(f"{s1}") +# s2 = set(arr2) +# print(f"Region1 after creating set2: {r}") +# print(f"Region2 after creating set2: {r2}") +# print(f"{s2}") +# input("Press Enter to create set xor...") +# result = s1 ^ s2 +# # result = s1.symmetric_difference(s2) +# print(f"Region1 after creating set xor: {r}") +# print(f"Region2 after creating set xor: {r2}") +# print(f"Xor result: {result}") \ No newline at end of file diff --git a/test_code/set_test/setisub_test1.py b/test_code/set_test/setisub_test1.py new file mode 100644 index 00000000000000..14b6fb270373fa --- /dev/null +++ b/test_code/set_test/setisub_test1.py @@ -0,0 +1,55 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable + +r = Region() +r2 = Region() +print(f"Initial Region: {r}") + +class A: pass; +freeze(A()) + +r.a = A() +r.b = A() +r.c = A() +r2.d = A() +r2.e = A() +f = A() +g = A() + +# r.arr1 = [r.a, r.b, r.c] +# r.arr2 = [r.a] +# s1 = set(r.arr1) +# print(f"Region after creating set1: {r}") +# print(f"{s1}") +# s2 = set(r.arr2) +# print(f"Region after creating set2: {r}") +# print(f"{s2}") +# input("Press Enter to create set difference...") +# s1 -= s2 +# print(f"Region after creating set difference: {r}") +# print(f"{s1}") + +r.arr1 = [r.a, r.b, r.c] +r.arr2 = [r.a] +r.s1 = set(r.arr1) +print(f"Region after creating set1: {r}") +print(f"{r.s1}") +s2 = set(r.arr2) +print(f"Region after creating set2: {r}") +print(f"{s2}") +input("Press Enter to create set difference...") +r.s1 -= s2 +# r.s1.difference_update(s2) +print(f"Region after creating set difference: {r}") +print(f"{r.s1}") + +# s1 = set(r.arr1) +# print(f"Region after creating set1: {r}") +# s2 = set(r.arr2) +# print(f"Region after creating set2: {r}") +# ss1 = set(s1) +# print(f"Region after creating set from set1: {r}") +# ss2 = set(s2) +# print(f"Region after creating set from set2: {r}") +# ss1 -= ss2 +# print(f"Region after creating set difference of set: {r}") diff --git a/test_code/set_test/setiter_test1.py b/test_code/set_test/setiter_test1.py new file mode 100644 index 00000000000000..fb1be61696acfc --- /dev/null +++ b/test_code/set_test/setiter_test1.py @@ -0,0 +1,56 @@ +# Test Case for testing setiterobject's dealloc and traverse functionsfrom regions import Region, is_local +from regions import Region, is_local +from immutable import freeze, register_freezable + +r = Region() +print(f"Initial Region: {r}") + +class A: pass; +freeze(A()) + +r.word=A() +r.word2=A() +r.word3=A() +r.word4=A() +r.word5=A() + +r.arr = [r.word, r.word2, r.word3, r.word4, r.word5] +print(f"Region after setting word: {r}") +s = set(r.arr) +print(f"Region after creating set: {r}") + +it = iter(s) +print(f"Region after creating set iterator: {r}") +# input("Press Enter to print set iterator...") +# r.it = it +# input("Press Enter to print set iterator...") +# print(f"Region after setting iterator: {r}") # iterator that "it" points to is moved into the region, so "s" as well. Since "s" is moved into the region, no borrowed references anymore from outside of the region. LRC is reset to the initial value. Then +2 from "it" and "s" variable itself. +# s=None +# it=None +# print(f"Region after setting set and iterator to None: {r}") + +a1 = next(it) +print(f"Region after calling next on set iterator: {r}") +a2 = next(it) +print(f"Region after calling next on set iterator again: {r}") +r.a3 = next(it) +print(f"Region after calling next on set iterator again: {r}") +a4 = next(it) +print(f"Region after calling next on set iterator again: {r}") +it = None +print(f"Region after setting iterator to None: {r}") + +# r.it = iter(s) +# print(f"Region after creating set iterator: {r}") +# print(f"{r.it}") +# print(f"Region after printing set iterator: {r}") +# a1 = next(r.it) +# print(f"Region after calling next on set iterator: {r}") +# a2 = next(r.it) +# print(f"Region after calling next on set iterator again: {r}") +# r.a3 = next(r.it) +# print(f"Region after calling next on set iterator again: {r}") +# a4 = next(r.it) +# print(f"Region after calling next on set iterator again: {r}") +# r.it = None +# print(f"Region after setting iterator to None: {r}") diff --git a/test_code/set_test/setixor_test1.py b/test_code/set_test/setixor_test1.py new file mode 100644 index 00000000000000..421f9a7d0e36c7 --- /dev/null +++ b/test_code/set_test/setixor_test1.py @@ -0,0 +1,68 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable + +r = Region() +r2 = Region() +print(f"Initial Region: {r}") + +class A: pass; +freeze(A()) + +r.a = A() +r.b = A() +r.c = A() +r.d = A() +r.e = A() +r.f = A() +r.arr1 = [r.a, r.b, r.c, r.f] +r.arr2 = [r.a, r.b, r.f] + +# s1 = set(r.arr1) +# print(f"Region after creating set1: {r}") # +4 +# print(f"{s1}") +# s2 = set(r.arr2) +# print(f"Region after creating set2: {r}") # +3 +# print(f"{s2}") +# input("Press Enter to create set xor...") +# # s1 ^= s2 +# s1.symmetric_difference_update(s2) # -4 for derefs r.a, r.b, r.c, r.f, then +1 for r.c +# print(f"Region after creating set xor: {r}") +# print(f"Xor result: {s1}") + +r.s1 = set(r.arr1) +print(f"Region after creating set1: {r}") # +4 +print(f"{r.s1}") +s2 = set(r.arr2) +print(f"Region after creating set2: {r}") # +3 +print(f"{s2}") +input("Press Enter to create set xor...") +# r.s1 ^= s2 +r.s1.symmetric_difference_update(s2) +print(f"Region after creating set xor: {r}") +print(f"Xor result: {r.s1}") + +# r.a = A() +# r.b = A() +# r.c = A() +# r2.d = A() +# r2.e = A() +# r2.f = A() +# r2.g = A() +# arr1 = [r.a, r.b, r.c, r2.d] +# arr2 = [r.c, r2.d, r2.e, r2.f, r2.g] +# print(f"Region1 before creating sets: {r}") +# print(f"Region2 before creating sets: {r2}") +# s1 = set(arr1) +# print(f"Region1 after creating set1: {r}") +# print(f"Region2 after creating set1: {r2}") +# print(f"{s1}") +# s2 = set(arr2) +# print(f"Region1 after creating set2: {r}") +# print(f"Region2 after creating set2: {r2}") +# print(f"{s2}") +# input("Press Enter to create set xor...") +# s1 ^= s2 +# # result = s1.symmetric_difference(s2) +# print(f"Region1 after creating set xor: {r}") +# print(f"Region2 after creating set xor: {r2}") +# print(f"Xor result: {s1}") \ No newline at end of file diff --git a/test_code/set_test/setor_test1.py b/test_code/set_test/setor_test1.py new file mode 100644 index 00000000000000..8ae81dcf5fe032 --- /dev/null +++ b/test_code/set_test/setor_test1.py @@ -0,0 +1,42 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable + +r = Region() +r2 = Region() +print(f"Initial Region: {r}") + +class A: pass; +freeze(A()) + +r.a = A() +r.b = A() +r.c = A() +r.d = A() +r.e = A() +r.f = A() +r.arr1 = [r.a, r.b, r.c] +r.arr2 = [r.b, r.c, r.f] + +# s1 = set(r.arr1) +# print(f"Region after creating set1: {r}") +# print(f"{s1}") +# s2 = set(r.arr2) +# print(f"Region after creating set2: {r}") +# print(f"{s2}") +# input("Press Enter to create set or...") +# # result = s1 | s2 +# result = s1.union(s2) +# print(f"Region after creating set or: {r}") +# print(f"Or result: {result}") + +r.s1 = set(r.arr1) +print(f"Region after creating set1: {r}") +print(f"{r.s1}") +s2 = set(r.arr2) +print(f"Region after creating set2: {r}") +print(f"{s2}") +input("Press Enter to create set or...") +result = r.s1 | s2 +# result = r.s1.union(s2) +print(f"Region after creating set or: {r}") +print(f"Or result: {result}") \ No newline at end of file diff --git a/test_code/set_test/setpopdiscard_test1.py b/test_code/set_test/setpopdiscard_test1.py new file mode 100644 index 00000000000000..f13f5f7802bdfb --- /dev/null +++ b/test_code/set_test/setpopdiscard_test1.py @@ -0,0 +1,29 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable + +r = Region() +print(f"Initial Region: {r}") +class A: pass +freeze(A()) + +r.a = A() +r.b = A() +r.c = A() +r.d = A() +r.e = A() +r.f = A() + +r.arr1 = [r.a, r.b, r.c] +print(f"Region after setting arr1: {r}") +s1 = set(r.arr1) +print(f"Region after creating set s1: {r}") + +# s1.pop() +# print(f"Region after popping from set s1: {r}") + +s1.discard(r.a) +print(f"Region after discarding r.a from set s1: {r}") +s1.discard(r.b) +print(f"Region after discarding r.b from set s1: {r}") +s1.discard(r.c) +print(f"Region after discarding r.c from set s1: {r}") diff --git a/test_code/set_test/setselfiter.py b/test_code/set_test/setselfiter.py new file mode 100644 index 00000000000000..909d27bacc8061 --- /dev/null +++ b/test_code/set_test/setselfiter.py @@ -0,0 +1,18 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable + +class A: pass +freeze(A()) + +r = Region() +print(f"Region r: {r}") +r.a = A() +r.b = A() +r.arr = [r.a, r.b] +print(f"Region r after creating arr: {r}") +r.s = set(r.arr) +print(f"Region r after creating set: {r}") +it = iter(r.s) +print(f"Region r after creating iterator for set: {r}") +r.it2 = iter(it) +print(f"Region r after creating iterator for iterator: {r}") \ No newline at end of file diff --git a/test_code/set_test/setsub_test1.py b/test_code/set_test/setsub_test1.py new file mode 100644 index 00000000000000..1eb071758fd16d --- /dev/null +++ b/test_code/set_test/setsub_test1.py @@ -0,0 +1,69 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable + +r = Region() +r2 = Region() +print(f"Initial Region: {r}") + +class A: pass; +freeze(A()) + +r.a = A() +r.b = A() +r.c = A() +r2.d = A() +r2.e = A() +f = A() +g = A() + +r.arr1 = [r.a, r.b, r.c] +r.arr2 = [r.a] +r.arr3 = [r.b] +s1 = set(r.arr1) +print(f"Region after creating set1: {r}") +print(f"{s1}") +s2 = set(r.arr2) +print(f"Region after creating set2: {r}") +print(f"{s2}") +s3 = set(r.arr3) +print(f"Region after creating set3: {r}") +print(f"{s3}") +input("Press Enter to create set difference...") +s4 = s1.difference(s2) +print(f"Region after creating set difference: {r}") +print(f"{s4}") + +# s1 = set(r.arr1) +# print(f"Region after creating set1: {r}") +# s2 = set(r.arr2) +# print(f"Region after creating set2: {r}") +# ss1 = set(s1) +# print(f"Region after creating set from set1: {r}") +# ss2 = set(s2) +# print(f"Region after creating set from set2: {r}") +# ss3 = ss1-ss2 +# print(f"Region after creating set difference of set: {r}") + +# s1 = set(r.arr1) +# print(f"Region after creating set1: {r}") +# print(f"{s1}") +# arr2 = [r.a] +# print(f"Region after creating set2: {r}") +# print(f"{arr2}") +# input("Press Enter to create set difference...") +# s3 = s1.difference(arr2) +# print(f"Region after creating set difference: {r}") +# print(f"{s3}") + +# arr3 = [r.a, r.b, r.c, r2.d] +# print(f"Region1 after creating arr3: {r}") +# print(f"Region2 after creating arr3: {r2}") +# s1 = set(arr3) +# print(f"Region after creating set1: {r}") +# print(f"Region2 after creating set1: {r2}") +# print(f"{s1}") +# input("Press Enter to create set difference...") +# s3 = s1.difference(r.arr1) +# print(f"Region after creating set difference: {r}") +# print(f"Region2 after creating set difference: {r2}") +# print(f"{s3}") \ No newline at end of file diff --git a/test_code/set_test/setxor_test1.py b/test_code/set_test/setxor_test1.py new file mode 100644 index 00000000000000..ae8421e2126ae6 --- /dev/null +++ b/test_code/set_test/setxor_test1.py @@ -0,0 +1,55 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable + +r = Region() +r2 = Region() +print(f"Initial Region: {r}") + +class A: pass; +freeze(A()) + +r.a = A() +r.b = A() +r.c = A() +r.d = A() +r.e = A() +r.f = A() +r.arr1 = [r.a, r.b, r.c] +r.arr2 = [r.b, r.c, r.f] +s1 = set(r.arr1) +print(f"Region after creating set1: {r}") +print(f"{s1}") +s2 = set(r.arr2) +print(f"Region after creating set2: {r}") +print(f"{s2}") +input("Press Enter to create set xor...") +# result = s1 ^ s2 +result = s1.symmetric_difference(s2) +print(f"Region after creating set xor: {r}") +print(f"Xor result: {result}") + +# r.a = A() +# r.b = A() +# r.c = A() +# r2.d = A() +# r2.e = A() +# r2.f = A() +# r2.g = A() +# arr1 = [r.a, r.b, r.c, r2.d] +# arr2 = [r.c, r2.d, r2.e, r2.f, r2.g] +# print(f"Region1 before creating sets: {r}") +# print(f"Region2 before creating sets: {r2}") +# s1 = set(arr1) +# print(f"Region1 after creating set1: {r}") +# print(f"Region2 after creating set1: {r2}") +# print(f"{s1}") +# s2 = set(arr2) +# print(f"Region1 after creating set2: {r}") +# print(f"Region2 after creating set2: {r2}") +# print(f"{s2}") +# input("Press Enter to create set xor...") +# result = s1 ^ s2 +# # result = s1.symmetric_difference(s2) +# print(f"Region1 after creating set xor: {r}") +# print(f"Region2 after creating set xor: {r2}") +# print(f"Xor result: {result}") \ No newline at end of file diff --git a/test_code/set_test/test1.py b/test_code/set_test/test1.py new file mode 100644 index 00000000000000..561ac5aa63f236 --- /dev/null +++ b/test_code/set_test/test1.py @@ -0,0 +1,44 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable + +r = Region() +print(f"Initial Region: {r}") +# r.word=2**1000 +# r.word2=2**2000 +# r.word3=2**3000 +class A: pass; +freeze(A()) + +r.word=A() +r.word2=A() + + +r.arr = [r.word, r.word2] +print(f"Region after setting word: {r}") +# input("Press Enter to create set...") +s = set(r.arr) +print(f"Region after creating set: {r}") +print(f"{s}") +print(f"Region after printing set: {r}") + +print("----------------------------------------------------------") +r2 = Region() +r2.word=A() +r2.word2=A() +word3=A() +word4=A() +print(f"New Region: {r2}") +r2.arr = [r2.word, r2.word2, word3, word4] # obj in word3 and word4 are added to the region r2, and word3 and word4 points to its object. +print(f"Region after setting word in new region: {r2}") +s2 = set(r2.arr) # Now, the set points to all objs in the region r2 +print(f"Region after creating set in new region: {r2}") + +# ------ Uncomment this makes an error to LRC count --------------- +# for i in s: + # print(hex(id(i))) + +# print(hex(id(r.word)), hex(id(r.word2)), hex(id(r.word3))) +# ----------------------------------------------------------------- + +s = None +print(f"Region after setting set to None: {r}") \ No newline at end of file diff --git a/test_code/set_test/test2.py b/test_code/set_test/test2.py new file mode 100644 index 00000000000000..c0080a324245f9 --- /dev/null +++ b/test_code/set_test/test2.py @@ -0,0 +1,38 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable + +r = Region() +print(f"Initial Region: {r}") + +class A: pass; +freeze(A()) + +r.word=A() +r.word2=A() +r.word3=A() +r.word4=A() +r.arr = [r.word, r.word2, r.word3, r.word4] +print(f"Region after setting word: {r}") # LRC=2 +s = set(r.arr) +print(f"Region after creating set: {r}") # LRC=6 since it borrows word, word2, word3, and word4. "s" does not matter here since set is not in the region yet. +print(f"{s}") +print(f"Region after printing set: {r}") # LRC=6 +r.set = s +print(f"Region after moving set into the region: {r}") # LRC=3: -4 from subtracting word, word2, word3, and word4 to the region since the set obj is moved into the region, +1 from s +input("Press Enter to create set...") +s2 = set(r.set) +print(f"Region after creating set from set in the region: {r}") +# print(f"{s2}") +# print(f"Region after printing set2: {r}") + +# print(f"{s}") + +# ------ Uncomment this makes an error to LRC count --------------- +# for i in s: +# print(hex(id(i))) +# print(f"Region after iterating through set: {r}") +# print(hex(id(r.word)), hex(id(r.word2)), hex(id(r.word3))) +# ----------------------------------------------------------------- + +s2 = None +print(f"Region after setting set to None: {r}") \ No newline at end of file diff --git a/test_code/set_test/test2_2.py b/test_code/set_test/test2_2.py new file mode 100644 index 00000000000000..e813a53e2acb37 --- /dev/null +++ b/test_code/set_test/test2_2.py @@ -0,0 +1,25 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable + +r = Region() +r2 = Region() +print(f"Initial Region: {r}") + +class A: pass; +freeze(A()) + +aa=A() +ab=A() +ac=A() +r2.word4=A() +# TODO: Test undo when 3 are in local, 1 is in another region +arr = [aa, ab, ac, r2.word4] +print(f"Region after setting word: {r}") # LRC=2 +s = set(arr) +print(f"Region after creating set: {r}") # LRC=6 since it borrows word, word2, word3, and word4. "s" does not matter here since set is not in the region yet. +try: + r.set = s +except Exception as e: + print(f"Error when moving set into the region: {e}") + print(f"{is_local(aa)}, {is_local(ab)}, {is_local(ac)}") + print(f"Region after moving set into the region: {r}") diff --git a/test_code/set_test/test3.py b/test_code/set_test/test3.py new file mode 100644 index 00000000000000..36b9d26847533b --- /dev/null +++ b/test_code/set_test/test3.py @@ -0,0 +1,23 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable, isfrozen + +r = Region() +print(f"Initial Region: {r}") + +class A: pass +freeze(A()) +r.word={A(): "value", A(): "value2"} +print(f"Region after setting word: {r}") +s = set(r.word) +print(f"Region after creating set: {r}") +print(f"{s}") + +print("\n") +r2 = Region() +print(f"New Region: {r2}") +r2.word = {"key": "value", "key2": "value2"} +print(f"isfrozen(r2.word): {isfrozen(r2.word["key"])}") +print(f"New Region after setting word: {r2}") +s2 = set(r2.word) +print(f"New Region after creating set: {r2}") +print(f"{s2}") \ No newline at end of file diff --git a/test_code/test1_1.py b/test_code/test1_1.py new file mode 100644 index 00000000000000..ff5bfd820c1699 --- /dev/null +++ b/test_code/test1_1.py @@ -0,0 +1,21 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable +import sys + +r = Region() +# freeze(10) +r.stop = 44543543543553534554 +print("sys.getrefcount(r.stop): ", sys.getrefcount(r.stop)) +print(f"{r.owns(r.stop)}") + +print(f"Initial Region: {r}") +#print(sys.getrefcount(r.stop)) +# print(f"{r.stop}") +ra = range(r.stop) +print(f"{ra}") +print(f"{r.owns(ra)}, {is_local(ra)}") +# print("sys.getrefcount(r.stop): ", sys.getrefcount(r.stop)) +#print(sys.getrefcount(r.stop)) +print(f"Region after setting stop: {r}") +ra = None +print(f"Region after setting ra to None: {r}") \ No newline at end of file diff --git a/test_code/test1_2.py b/test_code/test1_2.py new file mode 100644 index 00000000000000..693cf61f9474be --- /dev/null +++ b/test_code/test1_2.py @@ -0,0 +1,25 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable +import sys + +r = Region() +# freeze(10) +r.start = 44543543543553534000 +# r.start=3 +r.stop = 44543543543553534554 +print("sys.getrefcount(r.start): ", sys.getrefcount(r.start)) +print("sys.getrefcount(r.stop): ", sys.getrefcount(r.stop)) +print(f"{r.owns(r.start)}") +print(f"{r.owns(r.stop)}") + +print(f"Initial Region: {r}") +#print(sys.getrefcount(r.stop)) +# print(f"{r.stop}") +ra = range(r.start, r.stop) +print(f"{ra}") +print(f"{r.owns(ra)}, {is_local(ra)}") +# print("sys.getrefcount(r.stop): ", sys.getrefcount(r.stop)) +#print(sys.getrefcount(r.stop)) +print(f"Region after setting stop: {r}") +ra = None +print(f"Region after setting ra to None: {r}") \ No newline at end of file diff --git a/test_code/test1_3.py b/test_code/test1_3.py new file mode 100644 index 00000000000000..2b35d6c2620feb --- /dev/null +++ b/test_code/test1_3.py @@ -0,0 +1,34 @@ +from regions import Region, is_local +from immutable import freeze, register_freezable +import sys + +r = Region() +# freeze(10) +r.start = 44543543543553534000 +# r.start=3 +r.stop = 44543543543553534554 +# r.stop = 100 +r.step = 44543543543555 +# r.step = 10 +# a=10**20 +# b=100000000000000000000 +# print(f"{hex(id(a))}, {hex(id(b))}") +print("sys.getrefcount(r.start): ", sys.getrefcount(r.start)) +print("sys.getrefcount(r.stop): ", sys.getrefcount(r.stop)) +print("sys.getrefcount(r.step): ", sys.getrefcount(r.step)) +print(f"{r.owns(r.start)}") +print(f"{r.owns(r.stop)}") +print(f"{r.owns(r.step)}") + +print(f"Initial Region: {r}") +#print(sys.getrefcount(r.stop)) +# print(f"{r.stop}") +input("Press Enter to continue...") +ra = range(r.start, r.stop, r.step) +print(f"{ra}") +print(f"{r.owns(ra)}, {is_local(ra)}") +# print("sys.getrefcount(r.stop): ", sys.getrefcount(r.stop)) +#print(sys.getrefcount(r.stop)) +print(f"Region after setting stop: {r}") +ra = None +print(f"Region after setting ra to None: {r}") \ No newline at end of file