Skip to content

Commit defec9d

Browse files
committed
Support native tuple subclasses
1 parent 112a412 commit defec9d

File tree

8 files changed

+440
-155
lines changed

8 files changed

+440
-155
lines changed

graalpython/com.oracle.graal.python.cext/src/tupleobject.c

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -95,15 +95,6 @@ PyObject * tuple_subtype_new(PyTypeObject *type, PyObject *iterable) {
9595
return (PyObject*) newobj;
9696
}
9797

98-
int PyTruffle_Tuple_SetItem(PyObject* tuple, Py_ssize_t position, PyObject* item) {
99-
PyTuple_SET_ITEM(tuple, position, item);
100-
return 0;
101-
}
102-
103-
PyObject* PyTruffle_Tuple_GetItem(PyObject* tuple, Py_ssize_t position) {
104-
return PyTuple_GET_ITEM(tuple, position);
105-
}
106-
10798
PyObject* PyTruffle_Tuple_Alloc(PyTypeObject* cls, Py_ssize_t nitems) {
10899
/*
109100
* TODO(fa): For 'PyVarObjects' (i.e. 'nitems > 0') we increase the size by 'sizeof(void *)'
@@ -121,3 +112,7 @@ PyObject* PyTruffle_Tuple_Alloc(PyTypeObject* cls, Py_ssize_t nitems) {
121112
return newObj;
122113
}
123114

115+
void* PyTruffle_NativeTupleItems(PyTupleObject* tuple) {
116+
return polyglot_from_PyObjectPtr_array(tuple->ob_item, tuple->ob_base.ob_size);
117+
}
118+

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

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
22
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
33
#
44
# The Universal Permissive License (UPL), Version 1.0
@@ -36,8 +36,10 @@
3636
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
3737
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3838
# SOFTWARE.
39+
import unittest
40+
41+
from . import CPyExtTestCase, CPyExtFunction, CPyExtFunctionOutVars, unhandled_error_compare, GRAALPYTHON, CPyExtType
3942

40-
from . import CPyExtTestCase, CPyExtFunction, CPyExtFunctionOutVars, unhandled_error_compare, GRAALPYTHON
4143
__dir__ = __file__.rpartition("/")[0]
4244

4345

@@ -48,6 +50,14 @@ def _reference_getslice(args):
4850
return t[start:end]
4951

5052

53+
def _reference_getitem(args):
54+
t = args[0]
55+
idx = args[1]
56+
if idx < 0 or idx >= len(t):
57+
raise IndexError('tuple index out of range')
58+
return t[idx]
59+
60+
5161
class MyStr(str):
5262

5363
def __init__(self, s):
@@ -57,6 +67,24 @@ def __repr__(self):
5767
return self.s
5868

5969

70+
TupleSubclass = CPyExtType(
71+
"TupleSubclass",
72+
"""
73+
static PyObject* tuple_subclass_new(PyTypeObject* type, PyObject* args, PyObject* kwargs) {
74+
args = Py_BuildValue("(O)", args);
75+
if (args == NULL)
76+
return NULL;
77+
PyObject* result = PyTuple_Type.tp_new(type, args, kwargs);
78+
return result;
79+
}
80+
""",
81+
tp_base='&PyTuple_Type',
82+
tp_new='tuple_subclass_new',
83+
tp_alloc='0',
84+
tp_free='0',
85+
)
86+
87+
6088
class TestPyTuple(CPyExtTestCase):
6189

6290
def compile_module(self, name):
@@ -70,6 +98,7 @@ def compile_module(self, name):
7098
(tuple(),),
7199
((1, 2, 3),),
72100
(("a", "b"),),
101+
(TupleSubclass(1, 2, 3),),
73102
),
74103
resultspec="n",
75104
argspec='O',
@@ -83,6 +112,7 @@ def compile_module(self, name):
83112
(tuple(),),
84113
((1, 2, 3),),
85114
(("a", "b"),),
115+
(TupleSubclass(1, 2, 3),),
86116
# no type checking, also accepts different objects
87117
([1, 2, 3, 4],),
88118
({"a": 1, "b":2},),
@@ -92,6 +122,21 @@ def compile_module(self, name):
92122
arguments=["PyObject* tuple"],
93123
)
94124

125+
# PyTuple_GetItem
126+
test_PyTuple_GetItem = CPyExtFunctionOutVars(
127+
_reference_getitem,
128+
lambda: (
129+
((1, 2, 3), 1),
130+
(TupleSubclass(1, 2, 3), 1),
131+
((1, 2, 3), -1),
132+
((1, 2, 3), 3),
133+
),
134+
resultspec="O",
135+
argspec='On',
136+
arguments=["PyObject* tuple", "Py_ssize_t index"],
137+
resulttype="PyObject*",
138+
)
139+
95140
# PyTuple_GetSlice
96141
test_PyTuple_GetSlice = CPyExtFunctionOutVars(
97142
_reference_getslice,
@@ -100,6 +145,7 @@ def compile_module(self, name):
100145
((1, 2, 3), 0, 2),
101146
((4, 5, 6), 1, 2),
102147
((7, 8, 9), 2, 2),
148+
(TupleSubclass(1, 2, 3), 1, 2),
103149
),
104150
resultspec="O",
105151
argspec='Onn',
@@ -123,6 +169,7 @@ def compile_module(self, name):
123169
lambda: (
124170
(tuple(),),
125171
(("hello", "world"),),
172+
(TupleSubclass(1, 2, 3),),
126173
((None,),),
127174
([],),
128175
({},),
@@ -139,6 +186,7 @@ def compile_module(self, name):
139186
lambda: (
140187
(tuple(),),
141188
(("hello", "world"),),
189+
(TupleSubclass(1, 2, 3),),
142190
((None,),),
143191
([],),
144192
({},),
@@ -148,3 +196,22 @@ def compile_module(self, name):
148196
arguments=["PyObject* o"],
149197
cmpfunc=unhandled_error_compare
150198
)
199+
200+
201+
class TestNativeSubclass(unittest.TestCase):
202+
def test_builtins(self):
203+
t = TupleSubclass(1, 2, 3)
204+
assert t
205+
assert len(t) == 3
206+
assert t[1] == 2
207+
assert t[1:] == (2, 3)
208+
assert t == (1, 2, 3)
209+
assert t > (1, 2, 2)
210+
assert t < (1, 2, 4)
211+
assert 2 in t
212+
assert t + (4, 5) == (1, 2, 3, 4, 5)
213+
assert t * 2 == (1, 2, 3, 1, 2, 3)
214+
assert list(t) == [1, 2, 3]
215+
assert repr(t) == "(1, 2, 3)"
216+
assert t.index(2) == 1
217+
assert t.count(2) == 1

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextTupleBuiltins.java

Lines changed: 82 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,21 @@
5555
import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiUnaryBuiltinNode;
5656
import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.PromoteBorrowedValue;
5757
import com.oracle.graal.python.builtins.objects.PNone;
58+
import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject;
5859
import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes;
60+
import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.GetItemNode;
5961
import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.GetItemScalarNode;
6062
import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.ListGeneralizationNode;
63+
import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.SetItemNode;
6164
import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.SetItemScalarNode;
6265
import com.oracle.graal.python.builtins.objects.tuple.PTuple;
63-
import com.oracle.graal.python.builtins.objects.tuple.TupleBuiltins.GetItemNode;
6466
import com.oracle.graal.python.lib.PySliceNew;
67+
import com.oracle.graal.python.lib.PyTupleSizeNode;
6568
import com.oracle.graal.python.nodes.ErrorMessages;
69+
import com.oracle.graal.python.nodes.builtins.TupleNodes.GetNativeTupleStorage;
6670
import com.oracle.graal.python.runtime.sequence.storage.SequenceStorage;
6771
import com.oracle.truffle.api.dsl.Cached;
72+
import com.oracle.truffle.api.dsl.Cached.Shared;
6873
import com.oracle.truffle.api.dsl.Fallback;
6974
import com.oracle.truffle.api.dsl.Specialization;
7075
import com.oracle.truffle.api.profiles.ConditionProfile;
@@ -83,16 +88,41 @@ PTuple doGeneric(long size) {
8388
@CApiBuiltin(ret = Int, args = {PyObject, Py_ssize_t, PyObjectTransfer}, call = Direct)
8489
public abstract static class PyTuple_SetItem extends CApiTernaryBuiltinNode {
8590
@Specialization
86-
static int doManaged(PTuple tuple, Object position, Object element,
87-
@Cached("createForList()") SequenceStorageNodes.SetItemNode setItemNode,
91+
int doManaged(PTuple tuple, long index, Object element,
92+
@Shared("setItem") @Cached("createSetItem()") SequenceStorageNodes.SetItemNode setItemNode,
8893
@Cached ConditionProfile generalizedProfile) {
89-
setItemNode.execute(null, tuple.getSequenceStorage(), position, element);
90-
SequenceStorage newStorage = setItemNode.execute(null, tuple.getSequenceStorage(), position, element);
94+
SequenceStorage newStorage = setItem(tuple.getSequenceStorage(), index, element, setItemNode);
9195
if (generalizedProfile.profile(tuple.getSequenceStorage() != newStorage)) {
9296
tuple.setSequenceStorage(newStorage);
9397
}
9498
return 0;
9599
}
100+
101+
@Specialization
102+
int doNative(PythonAbstractNativeObject tuple, long index, Object element,
103+
@Cached GetNativeTupleStorage asNativeStorage,
104+
@Shared("setItem") @Cached("createSetItem()") SequenceStorageNodes.SetItemNode setItemNode) {
105+
setItem(asNativeStorage.execute(tuple), index, element, setItemNode);
106+
return 0;
107+
}
108+
109+
@Fallback
110+
@SuppressWarnings("unused")
111+
Object fallback(Object tuple, Object index, Object element) {
112+
throw raiseFallback(tuple, PythonBuiltinClassType.PTuple);
113+
}
114+
115+
private SequenceStorage setItem(SequenceStorage sequenceStorage, long index, Object element, SetItemNode setItemNode) {
116+
// we must do a bounds-check but we must not normalize the index
117+
if (index < 0 || index >= sequenceStorage.length()) {
118+
throw raise(IndexError, ErrorMessages.TUPLE_OUT_OF_BOUNDS);
119+
}
120+
return setItemNode.execute(null, sequenceStorage, (int) index, element);
121+
}
122+
123+
protected static SetItemNode createSetItem() {
124+
return SetItemNode.create(null, ListGeneralizationNode::create);
125+
}
96126
}
97127

98128
@CApiBuiltin(ret = PyObjectBorrowed, args = {PyObject, Py_ssize_t}, call = Direct)
@@ -102,21 +132,35 @@ public abstract static class PyTuple_GetItem extends CApiBinaryBuiltinNode {
102132

103133
@Specialization
104134
Object doPTuple(PTuple tuple, long key,
105-
@Cached PromoteBorrowedValue promoteNode,
135+
@Shared("promote") @Cached PromoteBorrowedValue promoteNode,
106136
@Cached ListGeneralizationNode generalizationNode,
107-
@Cached SetItemScalarNode setItemNode,
108-
@Cached GetItemScalarNode getItemNode) {
137+
@Shared("setItem") @Cached SetItemScalarNode setItemNode,
138+
@Shared("getItem") @Cached GetItemScalarNode getItemNode) {
109139
SequenceStorage sequenceStorage = tuple.getSequenceStorage();
110-
// we must do a bounds-check but we must not normalize the index
111-
if (key < 0 || key >= sequenceStorage.length()) {
112-
throw raise(IndexError, ErrorMessages.TUPLE_OUT_OF_BOUNDS);
113-
}
114-
Object result = getItemNode.execute(sequenceStorage, (int) key);
140+
int index = checkIndex(key, sequenceStorage);
141+
Object result = getItemNode.execute(sequenceStorage, index);
115142
Object promotedValue = promoteNode.execute(result);
116143
if (promotedValue != null) {
117144
sequenceStorage = generalizationNode.execute(sequenceStorage, promotedValue);
118145
tuple.setSequenceStorage(sequenceStorage);
119-
setItemNode.execute(sequenceStorage, (int) key, promotedValue);
146+
setItemNode.execute(sequenceStorage, index, promotedValue);
147+
return promotedValue;
148+
}
149+
return result;
150+
}
151+
152+
@Specialization
153+
Object doNative(PythonAbstractNativeObject tuple, long key,
154+
@Cached GetNativeTupleStorage asNativeStorage,
155+
@Shared("promote") @Cached PromoteBorrowedValue promoteNode,
156+
@Shared("setItem") @Cached SetItemScalarNode setItemNode,
157+
@Shared("getItem") @Cached GetItemScalarNode getItemNode) {
158+
SequenceStorage sequenceStorage = asNativeStorage.execute(tuple);
159+
int index = checkIndex(key, sequenceStorage);
160+
Object result = getItemNode.execute(sequenceStorage, index);
161+
Object promotedValue = promoteNode.execute(result);
162+
if (promotedValue != null) {
163+
setItemNode.execute(sequenceStorage, index, promotedValue);
120164
return promotedValue;
121165
}
122166
return result;
@@ -126,13 +170,21 @@ Object doPTuple(PTuple tuple, long key,
126170
Object fallback(Object tuple, @SuppressWarnings("unused") Object pos) {
127171
throw raiseFallback(tuple, PythonBuiltinClassType.PTuple);
128172
}
173+
174+
private int checkIndex(long key, SequenceStorage sequenceStorage) {
175+
// we must do a bounds-check but we must not normalize the index
176+
if (key < 0 || key >= sequenceStorage.length()) {
177+
throw raise(IndexError, ErrorMessages.TUPLE_OUT_OF_BOUNDS);
178+
}
179+
return (int) key;
180+
}
129181
}
130182

131183
@CApiBuiltin(ret = Py_ssize_t, args = {PyObject}, call = Direct)
132184
public abstract static class PyTuple_Size extends CApiUnaryBuiltinNode {
133185
@Specialization
134186
public static int size(Object tuple,
135-
@Cached com.oracle.graal.python.lib.PyTupleSizeNode pyTupleSizeNode) {
187+
@Cached PyTupleSizeNode pyTupleSizeNode) {
136188
return pyTupleSizeNode.execute(tuple);
137189
}
138190
}
@@ -141,15 +193,27 @@ public static int size(Object tuple,
141193
abstract static class PyTuple_GetSlice extends CApiTernaryBuiltinNode {
142194
@Specialization
143195
Object getSlice(PTuple tuple, Object iLow, Object iHigh,
144-
@Cached GetItemNode getItemNode,
145-
@Cached PySliceNew sliceNode) {
146-
return getItemNode.execute(null, tuple, sliceNode.execute(iLow, iHigh, PNone.NONE));
196+
@Shared("getItem") @Cached("createForTuple()") SequenceStorageNodes.GetItemNode getItemNode,
197+
@Shared("newSlice") @Cached PySliceNew sliceNode) {
198+
return doGetSlice(tuple.getSequenceStorage(), iLow, iHigh, getItemNode, sliceNode);
199+
}
200+
201+
@Specialization
202+
Object doNative(PythonAbstractNativeObject tuple, Object iLow, Object iHigh,
203+
@Shared("getItem") @Cached("createForTuple()") SequenceStorageNodes.GetItemNode getItemNode,
204+
@Shared("newSlice") @Cached PySliceNew sliceNode,
205+
@Cached GetNativeTupleStorage asNativeStorage) {
206+
return doGetSlice(asNativeStorage.execute(tuple), iLow, iHigh, getItemNode, sliceNode);
147207
}
148208

149209
@SuppressWarnings("unused")
150210
@Fallback
151211
Object fallback(Object tuple, Object iLow, Object iHigh) {
152212
throw raiseFallback(tuple, PythonBuiltinClassType.PTuple);
153213
}
214+
215+
private static Object doGetSlice(SequenceStorage storage, Object iLow, Object iHigh, GetItemNode getItemNode, PySliceNew sliceNode) {
216+
return getItemNode.execute(null, storage, sliceNode.execute(iLow, iHigh, PNone.NONE));
217+
}
154218
}
155219
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/NativeCAPISymbol.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,12 @@ public enum NativeCAPISymbol implements NativeCExtSymbol {
157157
FUN_PY_TRUFFLE_OBJECT_ARRAY_REALLOC("PyTruffle_ObjectArrayRealloc"),
158158
FUN_PY_TRUFFLE_PRIMITIVE_ARRAY_FREE("PyTruffle_PrimitiveArrayFree"),
159159
FUN_PY_TRUFFLE_OBJECT_ARRAY_FREE("PyTruffle_ObjectArrayFree"),
160+
FUN_PY_TRUFFLE_NATIVE_TUPLE_ITEMS("PyTruffle_NativeTupleItems"),
160161
FUN_PY_OBJECT_GENERIC_GET_DICT("_PyObject_GenericGetDict"),
161162
FUN_PY_OBJECT_NEW("PyTruffle_Object_New"),
162163
FUN_GET_THREAD_STATE_TYPE_ID("get_thread_state_typeid"),
163164
FUN_GET_PY_BUFFER_TYPEID("get_Py_buffer_typeid"),
164165
FUN_ADD_NATIVE_SLOTS("PyTruffle_Type_AddSlots"),
165-
FUN_PY_TRUFFLE_TUPLE_SET_ITEM("PyTruffle_Tuple_SetItem"),
166-
FUN_PY_TRUFFLE_TUPLE_GET_ITEM("PyTruffle_Tuple_GetItem"),
167166
FUN_PY_TRUFFLE_OBJECT_SIZE("PyTruffle_Object_Size"),
168167
FUN_PY_TYPE_READY("PyType_Ready"),
169168
FUN_GET_NEWFUNC_TYPE_ID("get_newfunc_typeid"),

0 commit comments

Comments
 (0)