Skip to content

Commit 7739539

Browse files
committed
Cache native slot wrappers + test of tp_setattr vs tp_setattro inheritance
1 parent ba66049 commit 7739539

File tree

3 files changed

+57
-5
lines changed

3 files changed

+57
-5
lines changed

graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_tp_slots.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,21 @@
4747
static PyObject* get_tp_attro(PyObject* unused, PyObject* object) {
4848
return PyLong_FromVoidPtr(Py_TYPE(object)->tp_getattro);
4949
}
50+
static PyObject* get_tp_setattr(PyObject* unused, PyObject* object) {
51+
return PyLong_FromVoidPtr(Py_TYPE(object)->tp_setattr);
52+
}
53+
static PyObject* get_tp_setattro(PyObject* unused, PyObject* object) {
54+
return PyLong_FromVoidPtr(Py_TYPE(object)->tp_setattro);
55+
}
5056
static PyObject* get_tp_descr_get(PyObject* unused, PyObject* object) {
5157
return PyLong_FromVoidPtr(Py_TYPE(object)->tp_descr_get);
5258
}
5359
""",
5460
tp_methods=
5561
'{"get_tp_attr", (PyCFunction)get_tp_attr, METH_O | METH_STATIC, ""},' +
5662
'{"get_tp_attro", (PyCFunction)get_tp_attro, METH_O | METH_STATIC, ""},' +
63+
'{"get_tp_setattr", (PyCFunction)get_tp_setattr, METH_O | METH_STATIC, ""},' +
64+
'{"get_tp_setattro", (PyCFunction)get_tp_setattro, METH_O | METH_STATIC, ""},' +
5765
'{"get_tp_descr_get", (PyCFunction)get_tp_descr_get, METH_O | METH_STATIC, ""}')
5866

5967

@@ -205,6 +213,45 @@ def test_setattr_wrapper():
205213
assert x.__setattr__("bar", 42) is None
206214

207215

216+
def test_setattr_vs_setattro_inheritance():
217+
TestSetAttrOInheritance = CPyExtType("TestSetAttrOInheritance",
218+
'''
219+
int testattr_set(PyObject* self, char* key, PyObject* value) {
220+
Py_XDECREF(((TestSetAttrOInheritanceObject*)self)->payload);
221+
if (value != NULL) {
222+
Py_INCREF(value);
223+
}
224+
((TestSetAttrOInheritanceObject*)self)->payload = value;
225+
return 0;
226+
}
227+
228+
PyObject* get_payload(PyObject *self) {
229+
PyObject* r = ((TestSetAttrOInheritanceObject*)self)->payload;
230+
if (r == NULL) Py_RETURN_NONE;
231+
Py_INCREF(r);
232+
return r;
233+
}
234+
''',
235+
cmembers='PyObject* payload;',
236+
tp_methods='{"get_payload", (PyCFunction)get_payload, METH_NOARGS, ""}',
237+
tp_setattr="testattr_set")
238+
239+
x = TestSetAttrOInheritance()
240+
x.foo = 42
241+
assert x.get_payload() == 42
242+
243+
class Managed(TestSetAttrOInheritance):
244+
pass
245+
246+
x = Managed()
247+
x.foo = 42 # calls slot_tp_setattro, which calls __setattr__, which wraps the object.tp_setattro
248+
assert x.get_payload() is None
249+
250+
assert SlotsGetter.get_tp_setattro(object()) == SlotsGetter.get_tp_setattro(Managed())
251+
assert SlotsGetter.get_tp_setattro(TestSetAttrOInheritance()) == 0
252+
assert SlotsGetter.get_tp_setattr(TestSetAttrOInheritance()) != 0
253+
254+
208255
def test_sq_ass_item_wrapper():
209256
TestSqAssItemWrapperReturn = CPyExtType("TestSqAssItemWrapperReturn",
210257
"static int myassitem(PyObject *self, Py_ssize_t i, PyObject *v) { return 0; }",

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TpSlots.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,18 +206,18 @@ private interface TpSlotGetter {
206206
*/
207207
@FunctionalInterface
208208
private interface NativeWrapperFactory {
209-
PyProcsWrapper create(TpSlotManaged slot);
209+
TpSlotWrapper create(TpSlotManaged slot);
210210

211211
final class Unimplemented implements NativeWrapperFactory {
212212
@Override
213-
public PyProcsWrapper create(TpSlotManaged slot) {
213+
public TpSlotWrapper create(TpSlotManaged slot) {
214214
throw new IllegalStateException("TODO: " + slot);
215215
}
216216
}
217217

218218
record ShouldNotReach(String slotName) implements NativeWrapperFactory {
219219
@Override
220-
public PyProcsWrapper create(TpSlotManaged slot) {
220+
public TpSlotWrapper create(TpSlotManaged slot) {
221221
throw new IllegalStateException(String.format("Slot %s should never be assigned a managed slot value.", slotName));
222222
}
223223
}
@@ -406,7 +406,7 @@ public Object readFromNative(PythonAbstractNativeObject pythonClass) {
406406
return field;
407407
}
408408

409-
public PyProcsWrapper createNativeWrapper(TpSlotManaged slot) {
409+
public TpSlotWrapper createNativeWrapper(TpSlotManaged slot) {
410410
return nativeWrapperFactory.create(slot);
411411
}
412412

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/slots/TpSlot.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,11 @@ public static Object toNative(TpSlotMeta slotMeta, TpSlot slot, Object defaultVa
8888
// This returns PyProcsWrapper, which will, in its toNative message, register the
8989
// pointer in C API context, such that we can map back from a pointer that we get from C
9090
// to the PyProcsWrapper and from that to the slot instance again in TpSlots#fromNative
91-
return slotMeta.createNativeWrapper(managedSlot);
91+
assert PythonContext.get(null).ownsGil(); // without GIL: use AtomicReference & CAS
92+
if (managedSlot.slotWrapper == null) {
93+
managedSlot.slotWrapper = slotMeta.createNativeWrapper(managedSlot);
94+
}
95+
return managedSlot.slotWrapper;
9296
} else {
9397
throw CompilerDirectives.shouldNotReachHere("TpSlotWrapper should wrap only managed slots. Native slots should go directly to native unwrapped.");
9498
}
@@ -121,6 +125,7 @@ public static TpSlot fromNative(PythonContext ctx, Object ptr, InteropLibrary in
121125
* Marker base class for managed slots: either builtin slots or user defined Python slots.
122126
*/
123127
public abstract static sealed class TpSlotManaged extends TpSlot permits TpSlotBuiltin, TpSlotPython {
128+
private TpSlotWrapper slotWrapper;
124129
}
125130

126131
/**

0 commit comments

Comments
 (0)