Skip to content

Commit 831dd66

Browse files
committed
Pyrona: Proper LRC decs and incs
1 parent 926b568 commit 831dd66

File tree

2 files changed

+82
-9
lines changed

2 files changed

+82
-9
lines changed

Lib/test/test_veronapy_writebarrier.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import unittest
2+
import dis
23

34
class TestCellWriteBarrier(unittest.TestCase):
45
class A:
@@ -75,5 +76,30 @@ def test_cell_replace_same_bridge(self):
7576
# since this writes replaces the previous owning reference
7677
c.cell_contents = child
7778

79+
class TestBytecodeWriteBarrier(unittest.TestCase):
80+
def test_delete_deref(self):
81+
r = Region()
82+
r.field = {}
83+
x = r.field
84+
85+
# Make sure the region knows about local reference from x
86+
self.assertFalse(r.try_close())
87+
88+
def del_x():
89+
nonlocal x
90+
del x # This triggers the DELETE_DEREF opcode
91+
92+
# Create the function and disassemble to confirm DELETE_DEREF is present
93+
bytecode = dis.Bytecode(del_x)
94+
self.assertIn("DELETE_DEREF", [instr.opname for instr in bytecode])
95+
96+
self.assertTrue(r.is_open())
97+
98+
# Call the function forcing the deletion of x which should
99+
# close the region.
100+
del_x()
101+
102+
self.assertFalse(r.is_open())
103+
78104
if __name__ == "__main__":
79105
unittest.main()

Objects/regions.c

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,33 @@ static void regionmetadata_inc_osc(Py_region_ptr_t self_ptr)
247247
#define regionmetadata_inc_osc(self) \
248248
(regionmetadata_inc_osc(REGION_PTR_CAST(self)))
249249

250+
/// This function checks the internal state, meaning the LRC, OSC and dirty
251+
/// status, to determine if the region should be closed. If possible, it will
252+
/// close the region and propagate the state to any parent region or cowns.
253+
///
254+
/// Returns `0` on success. An error might come from closing the region
255+
/// see `regionmetadata_close` for potential errors.
256+
static int regionmetadata_check_for_close(regionmetadata* self) {
257+
// If the region is marked as dirty, we can't trust the LRC or OSC
258+
// to update the status. The region has to be cleaned before it can
259+
// be closed
260+
if (regionmetadata_is_dirty(self)) {
261+
return 0;
262+
}
263+
264+
// The region has to remain open, if there are open subregions
265+
if (self->osc != 0) {
266+
return 0;
267+
}
268+
269+
// The region has to remain open, if there are references into the region.
270+
if (self->osc != 0) {
271+
return 0;
272+
}
273+
274+
return regionmetadata_close(self);
275+
}
276+
250277
/// Decrements the OSC of the region. This might close the region if the LRC
251278
/// and ORC both hit zero and the region is not marked as dirty.
252279
///
@@ -262,11 +289,7 @@ static int regionmetadata_dec_osc(Py_region_ptr_t self_ptr)
262289
self->osc -= 1;
263290

264291
// Check if the OSC decrease has closed this region as well.
265-
if (self->osc == 0 && self->lrc == 0 && !regionmetadata_is_dirty(self)) {
266-
return regionmetadata_close(self);
267-
}
268-
269-
return 0;
292+
return regionmetadata_check_for_close(self);
270293
}
271294
#define regionmetadata_dec_osc(self) \
272295
(regionmetadata_dec_osc(REGION_PTR_CAST(self)))
@@ -316,6 +339,31 @@ static int regionmetadata_dec_rc(Py_region_ptr_t self_ptr)
316339
#define regionmetadata_dec_rc(self) \
317340
(regionmetadata_dec_rc(REGION_PTR_CAST(self)))
318341

342+
static void regionmetadata_inc_lrc(regionmetadata* self)
343+
{
344+
assert(HAS_METADATA(self));
345+
346+
self->lrc += 1;
347+
regionmetadata_open(self);
348+
}
349+
#define regionmetadata_inc_lrc(self) \
350+
(regionmetadata_inc_lrc(REGION_DATA_CAST(self)))
351+
352+
/// Decrements the LRC of the region. This might close the region if the LRC
353+
/// and ORC both hit zero and the region is not marked as dirty.
354+
///
355+
/// Returns `0` on success. An error might come from closing the region
356+
/// see `regionmetadata_close` for potential errors.
357+
static int regionmetadata_dec_lrc(regionmetadata* self)
358+
{
359+
assert(HAS_METADATA(self));
360+
361+
self->lrc -= 1;
362+
return regionmetadata_check_for_close(self);
363+
}
364+
#define regionmetadata_dec_lrc(self) \
365+
(regionmetadata_dec_lrc(REGION_DATA_CAST(self)))
366+
319367
static void regionmetadata_set_parent(regionmetadata* self, regionmetadata* parent) {
320368
// Just a sanity check, since these cases should never happen
321369
assert(HAS_METADATA(self) && "Can't set the parent on the immutable and local region");
@@ -1928,7 +1976,7 @@ bool _Py_RegionAddReference(PyObject *src, PyObject *tgt) {
19281976
if (_Py_IsLocal(src)) {
19291977
// Record the borrowed reference in the LRC of the target region
19301978
// _Py_VPYDBG("Added borrowed ref %p --> %p (owner: '%s')\n", tgt, new_ref, get_region_name(tgt));
1931-
Py_REGION_DATA(tgt)->lrc += 1;
1979+
regionmetadata_inc_lrc(Py_REGION_DATA(tgt));
19321980
return true;
19331981
}
19341982

@@ -1991,9 +2039,8 @@ void _Py_RegionRemoveReference(PyObject *src, PyObject *tgt) {
19912039
regionmetadata* tgt_md = Py_REGION_DATA(tgt);
19922040
if (_Py_IsLocal(src)) {
19932041
// Dec LRC of the previously referenced region
1994-
// TODO should this decrement be a function, if it hits zero,
1995-
// then a region could become unreachable.
1996-
tgt_md->lrc -= 1;
2042+
// TODO should errors be propagated?
2043+
regionmetadata_dec_lrc(tgt_md);
19972044
return;
19982045
}
19992046

0 commit comments

Comments
 (0)