Skip to content

Commit d26beaf

Browse files
committed
Regions: Add Write-Barriers to bytecodes
1 parent 5e6afeb commit d26beaf

13 files changed

Lines changed: 295 additions & 31 deletions

File tree

Include/internal/pycore_cell.h

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,32 @@ PyCell_SwapTakeRef(PyCellObject *cell, PyObject *value, int* result)
2121
PyObject *old_value = NULL;
2222
*result = 0;
2323
Py_BEGIN_CRITICAL_SECTION(cell);
24-
if(Py_CHECKWRITE(cell)){
24+
do {
25+
if(!Py_CHECKWRITE(cell)){
26+
PyRegion_CLEARLOCAL(value);
27+
*result = -1;
28+
break;
29+
}
30+
31+
// Simple and fast clear
32+
if (PyRegion_IsLocal(cell)) {
33+
old_value = cell->ob_ref;
34+
FT_ATOMIC_STORE_PTR_RELEASE(cell->ob_ref, value);
35+
break;
36+
}
37+
2538
old_value = cell->ob_ref;
39+
if (PyRegion_AddLocalRef(old_value)) {
40+
assert(false && "This should never fail, since we have a ref to cell");
41+
}
42+
if (PyRegion_TakeRef(cell, value)) {
43+
PyRegion_CLEARLOCAL(value);
44+
*result = -1;
45+
break;
46+
}
2647
FT_ATOMIC_STORE_PTR_RELEASE(cell->ob_ref, value);
27-
}
28-
else {
29-
*result = -1;
30-
PyRegion_RemoveLocalRef(value);
31-
Py_XDECREF(value);
32-
}
48+
PyRegion_RemoveRef(cell, old_value);
49+
} while (false);
3350
Py_END_CRITICAL_SECTION();
3451
return old_value;
3552
}

Include/internal/pycore_opcode_metadata.h

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_uop_metadata.h

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import unittest
2+
from regions import Region
3+
from immutable import freeze
4+
5+
class TestCrashesObject(unittest.TestCase):
6+
7+
def ignore_test_exception_and_crash_on_freeze(self):
8+
r = Region()
9+
class A:
10+
def foo(self):
11+
r
12+
freeze(A)
13+
14+
def test_barrier_in_optimized_opcode(self):
15+
class A: pass
16+
17+
def build_region():
18+
r = Region()
19+
r.a = A()
20+
r.a.b = A()
21+
r.a = None
22+
return r
23+
24+
r1 = build_region()
25+
# This second call will optimize the function to use
26+
# `_STORE_ATTR_INSTANCE_VALUE` bytecode that can't
27+
# handle region barriers correctly. We therefore need
28+
# to de-opt again if the objects are in separate regions
29+
# like in the example above.
30+
r2 = build_region()
31+
r3 = build_region()

Objects/abstract.c

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ PyObject_GetItem(PyObject *o, PyObject *key)
166166

167167
PyMappingMethods *m = Py_TYPE(o)->tp_as_mapping;
168168
if (m && m->mp_subscript) {
169+
// TODO(regions): PyRegion_NotifyTypeUse(Py_TYPE(o));
169170
PyObject *item = m->mp_subscript(o, key);
170171
assert(_Py_CheckSlotResult(o, "__getitem__", item != NULL));
171172
return item;
@@ -199,10 +200,10 @@ PyObject_GetItem(PyObject *o, PyObject *key)
199200
}
200201
if (meth && meth != Py_None) {
201202
result = PyObject_CallOneArg(meth, key);
202-
Py_DECREF(meth);
203+
PyRegion_CLEARLOCAL(meth);
203204
return result;
204205
}
205-
Py_XDECREF(meth);
206+
PyRegion_CLEARLOCAL(meth);
206207
PyErr_Format(PyExc_TypeError, "type '%.200s' is not subscriptable",
207208
((PyTypeObject *)o)->tp_name);
208209
return NULL;
@@ -2906,12 +2907,13 @@ PyObject_GetIter(PyObject *o)
29062907
return type_error("'%.200s' object is not iterable", o);
29072908
}
29082909
else {
2910+
// FIXME(regions): xFrednet: PyRegion_NotifyTypeUse(t);
29092911
PyObject *res = (*f)(o);
29102912
if (res != NULL && !PyIter_Check(res)) {
29112913
PyErr_Format(PyExc_TypeError,
29122914
"%T.__iter__() must return an iterator, not %T",
29132915
o, res);
2914-
Py_SETREF(res, NULL);
2916+
PyRegion_CLEARLOCAL(res);
29152917
}
29162918
return res;
29172919
}
@@ -2957,11 +2959,8 @@ static int
29572959
iternext(PyObject *iter, PyObject **item)
29582960
{
29592961
iternextfunc tp_iternext = Py_TYPE(iter)->tp_iternext;
2960-
// Check if the type is Pyrona aware, otherwise, mark all open
2961-
// regions as dirty
2962-
// FIXME(regions): Enable this check, which currently almost always triggers
2963-
// PyRegion_NotifyTypeUse(Py_TYPE(iter));
29642962

2963+
// FIXME(regions): PyRegion_NotifyTypeUse(Py_TYPE(iter));
29652964
if ((*item = tp_iternext(iter))) {
29662965
return 1;
29672966
}

Objects/object.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1595,7 +1595,7 @@ _PyObject_GetDictPtr(PyObject *obj)
15951595
PyObject *
15961596
PyObject_SelfIter(PyObject *obj)
15971597
{
1598-
return Py_NewRef(obj);
1598+
return PyRegion_NewRef(obj);
15991599
}
16001600

16011601
/* Helper used when the __next__ method is removed from a type:

Objects/sliceobject.c

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,15 +128,24 @@ _PyBuildSlice_Consume2(PyObject *start, PyObject *stop, PyObject *step)
128128
}
129129
}
130130

131+
// This LRC/RC bump is needed to use one `TakeRefs` for
132+
// all three objects.
133+
if (PyRegion_NewRef(step) == NULL) {
134+
goto error;
135+
}
136+
if (PyRegion_TakeRefs(obj, start, stop, step)) {
137+
PyRegion_CLEARLOCAL(step);
138+
goto error;
139+
}
131140
obj->start = start;
132141
obj->stop = stop;
133-
obj->step = Py_NewRef(step);
142+
obj->step = step;
134143

135144
_PyObject_GC_TRACK(obj);
136145
return obj;
137146
error:
138-
Py_DECREF(start);
139-
Py_DECREF(stop);
147+
PyRegion_CLEARLOCAL(start);
148+
PyRegion_CLEARLOCAL(stop);
140149
return NULL;
141150
}
142151

@@ -152,6 +161,9 @@ PySlice_New(PyObject *start, PyObject *stop, PyObject *step)
152161
if (stop == NULL) {
153162
stop = Py_None;
154163
}
164+
if (PyRegion_AddLocalRefs(start, stop)) {
165+
return NULL;
166+
}
155167
return (PyObject *)_PyBuildSlice_Consume2(Py_NewRef(start),
156168
Py_NewRef(stop), step);
157169
}

Python/bytecodes.c

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,8 @@ dummy_func(
831831
assert(Py_REFCNT(left_o) >= 2 || !PyStackRef_IsHeapSafe(left));
832832
PyStackRef_CLOSE_SPECIALIZED(left, _PyUnicode_ExactDealloc);
833833
DEAD(left);
834+
// REGIONS: No write barrier needed, since these are all unicode
835+
// objects and therefore immutable when observed
834836
PyObject *temp = PyStackRef_AsPyObjectSteal(*target_local);
835837
PyObject *right_o = PyStackRef_AsPyObjectSteal(right);
836838
PyUnicode_Append(&temp, right_o);
@@ -882,6 +884,7 @@ dummy_func(
882884
}
883885

884886
op(_BINARY_SLICE, (container, start, stop -- res)) {
887+
// REGIONS: Write barrier called inside `_PyBuildSlice_ConsumeRefs`
885888
PyObject *slice = _PyBuildSlice_ConsumeRefs(PyStackRef_AsPyObjectSteal(start),
886889
PyStackRef_AsPyObjectSteal(stop));
887890
PyObject *res_o;
@@ -892,6 +895,8 @@ dummy_func(
892895
}
893896
else {
894897
res_o = PyObject_GetItem(PyStackRef_AsPyObjectBorrow(container), slice);
898+
// REGIONS: No barrier needed, since slice is local
899+
assert(PyRegion_IsLocal(slice));
895900
Py_DECREF(slice);
896901
}
897902
PyStackRef_CLOSE(container);
@@ -909,6 +914,7 @@ dummy_func(
909914
}
910915

911916
op(_STORE_SLICE, (v, container, start, stop -- )) {
917+
// Regions: Write barrier called inside the function
912918
PyObject *slice = _PyBuildSlice_ConsumeRefs(PyStackRef_AsPyObjectSteal(start),
913919
PyStackRef_AsPyObjectSteal(stop));
914920
int err;
@@ -917,6 +923,7 @@ dummy_func(
917923
}
918924
else {
919925
err = PyObject_SetItem(PyStackRef_AsPyObjectBorrow(container), slice, PyStackRef_AsPyObjectBorrow(v));
926+
assert(PyRegion_IsLocal(slice));
920927
Py_DECREF(slice);
921928
}
922929
DECREF_INPUTS();
@@ -1088,12 +1095,14 @@ dummy_func(
10881095
_PUSH_FRAME;
10891096

10901097
inst(LIST_APPEND, (list, unused[oparg-1], v -- list, unused[oparg-1])) {
1098+
// REGIONS: The write barrier is called in the function
10911099
int err = _PyList_AppendTakeRef((PyListObject *)PyStackRef_AsPyObjectBorrow(list),
10921100
PyStackRef_AsPyObjectSteal(v));
10931101
ERROR_IF(err < 0);
10941102
}
10951103

10961104
inst(SET_ADD, (set, unused[oparg-1], v -- set, unused[oparg-1])) {
1105+
// REGIONS: The write barrier is called in the function
10971106
int err = _PySet_AddTakeRef((PySetObject *)PyStackRef_AsPyObjectBorrow(set),
10981107
PyStackRef_AsPyObjectSteal(v));
10991108
ERROR_IF(err);
@@ -1138,6 +1147,7 @@ dummy_func(
11381147
// Ensure nonnegative, zero-or-one-digit ints.
11391148
DEOPT_IF(!_PyLong_IsNonNegativeCompact((PyLongObject *)sub));
11401149
Py_ssize_t index = ((PyLongObject*)sub)->long_value.ob_digit[0];
1150+
DEOPT_IF(!PyRegion_IsLocal(list));
11411151
DEOPT_IF(!LOCK_OBJECT(list));
11421152
// Ensure index < len(list)
11431153
if (index >= PyList_GET_SIZE(list)) {
@@ -1154,6 +1164,7 @@ dummy_func(
11541164
PyStackRef_CLOSE_SPECIALIZED(sub_st, _PyLong_ExactDealloc);
11551165
DEAD(sub_st);
11561166
PyStackRef_CLOSE(list_st);
1167+
PyRegion_RemoveLocalRef(old_value);
11571168
Py_DECREF(old_value);
11581169
}
11591170

@@ -1165,6 +1176,7 @@ dummy_func(
11651176

11661177
assert(PyDict_CheckExact(dict));
11671178
STAT_INC(STORE_SUBSCR, hit);
1179+
// REGIONS: The write barrier is called inside the function
11681180
int err = _PyDict_SetItem_Take2((PyDictObject *)dict,
11691181
PyStackRef_AsPyObjectSteal(sub),
11701182
PyStackRef_AsPyObjectSteal(value));
@@ -1460,13 +1472,15 @@ dummy_func(
14601472

14611473
inst(POP_EXCEPT, (exc_value -- )) {
14621474
_PyErr_StackItem *exc_info = tstate->exc_info;
1475+
// Regions: `exc_info->exc_value` is local
14631476
Py_XSETREF(exc_info->exc_value,
14641477
PyStackRef_IsNone(exc_value)
14651478
? NULL : PyStackRef_AsPyObjectSteal(exc_value));
14661479
}
14671480

14681481
tier1 inst(RERAISE, (values[oparg], exc_st -- values[oparg])) {
14691482
PyObject *exc = PyStackRef_AsPyObjectSteal(exc_st);
1483+
assert(PyRegion_IsLocal(exc));
14701484

14711485
assert(oparg >= 0 && oparg <= 2);
14721486
if (oparg) {
@@ -1605,8 +1619,10 @@ dummy_func(
16051619
}
16061620

16071621
op(_UNPACK_SEQUENCE, (seq -- unused[oparg], top[0])) {
1622+
// Regions: seq_o is local, the LRC is increased by the steal
16081623
PyObject *seq_o = PyStackRef_AsPyObjectSteal(seq);
16091624
int res = _PyEval_UnpackIterableStackRef(tstate, seq_o, oparg, -1, top);
1625+
PyRegion_RemoveLocalRef(seq_o);
16101626
Py_DECREF(seq_o);
16111627
ERROR_IF(res == 0);
16121628
}
@@ -1663,8 +1679,10 @@ dummy_func(
16631679
}
16641680

16651681
inst(UNPACK_EX, (seq -- unused[oparg & 0xFF], unused, unused[oparg >> 8], top[0])) {
1682+
// Regions: seq_o is local, the LRC is increased by the steal
16661683
PyObject *seq_o = PyStackRef_AsPyObjectSteal(seq);
16671684
int res = _PyEval_UnpackIterableStackRef(tstate, seq_o, oparg & 0xFF, oparg >> 8, top);
1685+
PyRegion_AddLocalRef(seq_o);
16681686
Py_DECREF(seq_o);
16691687
ERROR_IF(res == 0);
16701688
}
@@ -2640,16 +2658,29 @@ dummy_func(
26402658
ERROR_IF(true);
26412659
}
26422660
assert(_PyObject_GetManagedDict(owner_o) == NULL);
2661+
PyObject *value_o = PyStackRef_AsPyObjectBorrow(value);
2662+
DEOPT_IF(!PyRegion_IsLocal(owner_o) && !PyRegion_SameRegion(owner_o, value_o));
2663+
// This call can't escape, due to the DEOPT_IF guard above.
2664+
// As is, it should only update the LRC and or be a noop. In
2665+
// the worst case, it should only may mark a region as dirty.
2666+
if (PyRegion_AddRef(owner_o, value_o)) {
2667+
UNLOCK_OBJECT(owner_o);
2668+
DECREF_INPUTS();
2669+
ERROR_IF(true);
2670+
}
26432671
PyObject **value_ptr = (PyObject**)(((char *)owner_o) + offset);
26442672
PyObject *old_value = *value_ptr;
2645-
FT_ATOMIC_STORE_PTR_RELEASE(*value_ptr, PyStackRef_AsPyObjectSteal(value));
2673+
FT_ATOMIC_STORE_PTR_RELEASE(*value_ptr, Py_NewRef(value_o));
26462674
if (old_value == NULL) {
26472675
PyDictValues *values = _PyObject_InlineValues(owner_o);
26482676
Py_ssize_t index = value_ptr - values->values;
26492677
_PyDictValues_AddToInsertionOrder(values, index);
2678+
} else {
2679+
PyRegion_RemoveRef(owner_o, old_value);
26502680
}
26512681
UNLOCK_OBJECT(owner_o);
26522682
PyStackRef_CLOSE(owner);
2683+
PyStackRef_CLOSE(value);
26532684
Py_XDECREF(old_value);
26542685
}
26552686

@@ -2664,6 +2695,7 @@ dummy_func(
26642695
assert(Py_TYPE(owner_o)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
26652696
PyDictObject *dict = _PyObject_GetManagedDict(owner_o);
26662697
DEOPT_IF(dict == NULL);
2698+
DEOPT_IF(!PyRegion_IsLocal(dict));
26672699
DEOPT_IF(!LOCK_OBJECT(dict));
26682700
if (!Py_CHECKWRITE(owner_o))
26692701
{
@@ -2709,6 +2741,7 @@ dummy_func(
27092741
op(_STORE_ATTR_SLOT, (index/1, value, owner --)) {
27102742
PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner);
27112743

2744+
DEOPT_IF(!PyRegion_IsLocal(owner_o));
27122745
DEOPT_IF(!LOCK_OBJECT(owner_o));
27132746
// TODO(Immutable) If the dictionary object has been made and is immutable, then this should fail,
27142747
// but we aren't finding the dictionary object here? Can we do this efficiently enough?
@@ -4989,9 +5022,19 @@ dummy_func(
49895022

49905023
inst(SET_FUNCTION_ATTRIBUTE, (attr_st, func_in -- func_out)) {
49915024
PyObject *func = PyStackRef_AsPyObjectBorrow(func_in);
5025+
if (!Py_CHECKWRITE(func))
5026+
{
5027+
_PyEval_FormatExcNotWriteable(tstate, _PyFrame_GetCode(frame), oparg);
5028+
DECREF_INPUTS();
5029+
ERROR_IF(true);
5030+
}
49925031
PyObject *attr = PyStackRef_AsPyObjectSteal(attr_st);
49935032
func_out = func_in;
49945033
DEAD(func_in);
5034+
if (PyRegion_AddRef(func, attr)) {
5035+
DECREF_INPUTS();
5036+
ERROR_IF(true);
5037+
}
49955038
assert(PyFunction_Check(func));
49965039
size_t offset = _Py_FunctionAttributeOffsets[oparg];
49975040
assert(offset != 0);

0 commit comments

Comments
 (0)