Skip to content

Commit c93c402

Browse files
committed
Implement storing native object dict in object header
1 parent a94e3cc commit c93c402

File tree

8 files changed

+111
-66
lines changed

8 files changed

+111
-66
lines changed

graalpython/com.oracle.graal.python.cext/include/internal/pycore_object.h

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -203,17 +203,14 @@ _PyObject_IS_GC(PyObject *obj)
203203
// Fast inlined version of PyType_IS_GC()
204204
#define _PyType_IS_GC(t) _PyType_HasFeature((t), Py_TPFLAGS_HAVE_GC)
205205

206-
// GraalPy change: we don't want to pull in CPython GC implementation details
207-
/*
208206
static inline size_t
209207
_PyType_PreHeaderSize(PyTypeObject *tp)
210208
{
211-
return _PyType_IS_GC(tp) * sizeof(PyGC_Head) +
212-
_PyType_HasFeature(tp, Py_TPFLAGS_MANAGED_DICT) * 2 * sizeof(PyObject *);
209+
// GraalPy change: remove CPython's GC header; also we put only one pointer for dict, we don't store it inlined
210+
return _PyType_HasFeature(tp, Py_TPFLAGS_MANAGED_DICT) * sizeof(PyObject *);
213211
}
214212

215213
void _PyObject_GC_Link(PyObject *op);
216-
*/
217214

218215
// Usage: assert(_Py_CheckSlotResult(obj, "__getitem__", result != NULL));
219216
extern int _Py_CheckSlotResult(
@@ -240,14 +237,16 @@ PyObject * _PyObject_GetInstanceAttribute(PyObject *obj, PyDictValues *values,
240237

241238
static inline PyDictValues **_PyObject_ValuesPointer(PyObject *obj)
242239
{
243-
assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
244-
return ((PyDictValues **)obj)-4;
240+
// GraalPy change: we don't have inlined managed dict values
241+
assert(0);
242+
return NULL;
245243
}
246244

247245
static inline PyObject **_PyObject_ManagedDictPointer(PyObject *obj)
248246
{
249247
assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
250-
return ((PyObject **)obj)-3;
248+
// GraalPy change: ours is at a different offset
249+
return ((PyObject **)obj)-1;
251250
}
252251

253252
#define MANAGED_DICT_OFFSET (((int)sizeof(PyObject *))*-3)

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

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
*/
4141
#include "capi.h"
4242

43+
#include "pycore_object.h" // _PyType_CheckConsistency(), _Py_FatalRefcountError()
44+
4345

4446
PyObject* _PyDict_NewPresized(Py_ssize_t minused) {
4547
/* we ignore requests to capacity for now */
@@ -110,53 +112,67 @@ int PyDict_DelItemString(PyObject *d, const char *key) {
110112
CALL_WITH_STRING(key, int, -1, GraalPyDict_DelItem, d, string);
111113
}
112114

113-
PyAPI_FUNC(PyObject*) _PyObject_GenericGetDict(PyObject* obj) {
114-
PyObject** dictptr = _PyObject_GetDictPtr(obj);
115+
PyObject *
116+
PyObject_GenericGetDict(PyObject *obj, void *context)
117+
{
118+
PyObject *dict;
119+
// GraalPy change: we don't have inlined values in managed dict
120+
PyObject **dictptr = _PyObject_GetDictPtr(obj);
115121
if (dictptr == NULL) {
122+
PyErr_SetString(PyExc_AttributeError,
123+
"This object has no __dict__");
116124
return NULL;
117125
}
118-
PyObject* dict = *dictptr;
126+
dict = *dictptr;
119127
if (dict == NULL) {
128+
// GraalPy change: we don't have CPython's cache
120129
*dictptr = dict = PyDict_New();
121130
}
122131
Py_XINCREF(dict);
123132
return dict;
124133
}
125134

126-
PyObject* PyObject_GenericGetDict(PyObject* obj, void* context) {
127-
PyObject* d = _PyObject_GenericGetDict(obj);
128-
if (d == NULL) {
129-
PyErr_SetString(PyExc_AttributeError, "This object has no __dict__");
130-
}
131-
return d;
132-
}
133-
134-
PyObject** _PyObject_GetDictPtr(PyObject* obj) {
135+
PyObject **
136+
_PyObject_DictPointer(PyObject *obj)
137+
{
135138
Py_ssize_t dictoffset;
136-
137-
// relies on the fact that 'tp_dictoffset' is in sync with the corresponding managed class !
138139
PyTypeObject *tp = Py_TYPE(obj);
139140

141+
if (tp->tp_flags & Py_TPFLAGS_MANAGED_DICT) {
142+
return _PyObject_ManagedDictPointer(obj);
143+
}
140144
dictoffset = tp->tp_dictoffset;
141-
if (dictoffset == 0) {
145+
if (dictoffset == 0)
142146
return NULL;
143-
}
144147
if (dictoffset < 0) {
145-
Py_ssize_t nitems = ((PyVarObject *)obj)->ob_size;
146-
if (nitems < 0) {
147-
nitems = -nitems;
148+
Py_ssize_t tsize = Py_SIZE(obj);
149+
if (tsize < 0) {
150+
tsize = -tsize;
148151
}
152+
size_t size = _PyObject_VAR_SIZE(tp, tsize);
153+
assert(size <= (size_t)PY_SSIZE_T_MAX);
154+
dictoffset += (Py_ssize_t)size;
149155

150-
size_t size = tp->tp_basicsize + nitems * tp->tp_itemsize;
151-
if (size % SIZEOF_VOID_P != 0) {
152-
// round to full pointer boundary
153-
size += SIZEOF_VOID_P - (size % SIZEOF_VOID_P);
154-
}
155-
dictoffset += (long)size;
156+
_PyObject_ASSERT(obj, dictoffset > 0);
157+
_PyObject_ASSERT(obj, dictoffset % SIZEOF_VOID_P == 0);
156158
}
157159
return (PyObject **) ((char *)obj + dictoffset);
158160
}
159161

162+
/* Helper to get a pointer to an object's __dict__ slot, if any.
163+
* Creates the dict from inline attributes if necessary.
164+
* Does not set an exception.
165+
*
166+
* Note that the tp_dictoffset docs used to recommend this function,
167+
* so it should be treated as part of the public API.
168+
*/
169+
PyObject **
170+
_PyObject_GetDictPtr(PyObject *obj)
171+
{
172+
// GraalPy change: we don't have inlined managed dict values, so just use the common path
173+
return _PyObject_DictPointer(obj);
174+
}
175+
160176
/* Taken from CPython */
161177
int
162178
_PyDict_ContainsId(PyObject *op, struct _Py_Identifier *key)

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -916,8 +916,9 @@ PyVarObject* _PyObject_GC_NewVar(PyTypeObject *tp, Py_ssize_t nitems) {
916916
return _PyObject_NewVar(tp, nitems);
917917
}
918918

919-
void PyObject_GC_Del(void *tp) {
920-
PyObject_Free(tp);
919+
void PyObject_GC_Del(void *op) {
920+
size_t presize = _PyType_PreHeaderSize(((PyObject *)op)->ob_type);
921+
PyObject_Free(((char *)op)-presize);
921922
}
922923

923924
void PyObject_GC_Track(void* a) {

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,12 +1118,16 @@ _PyType_AllocNoTrack(PyTypeObject *type, Py_ssize_t nitems)
11181118
const size_t size = _PyObject_VAR_SIZE(type, nitems+1);
11191119
/* note that we need to add one, for the sentinel */
11201120

1121-
// GraalPy change: remove the GC header
1122-
char *alloc = PyObject_Malloc(size);
1121+
const size_t presize = _PyType_PreHeaderSize(type);
1122+
char *alloc = PyObject_Malloc(size + presize);
11231123
if (alloc == NULL) {
11241124
return PyErr_NoMemory();
11251125
}
1126-
obj = (PyObject *)alloc;
1126+
obj = (PyObject *)(alloc + presize);
1127+
if (presize) {
1128+
// GraalPy change: different header layout, no GC link
1129+
((PyObject **)alloc)[0] = NULL;
1130+
}
11271131
memset(obj, '\0', size);
11281132

11291133
if (type->tp_itemsize == 0) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ public enum NativeCAPISymbol implements NativeCExtSymbol {
125125
FUN_PY_TRUFFLE_OBJECT_ARRAY_RELEASE("PyTruffle_ObjectArrayRelease", ArgDescriptor.Void, Pointer, Int),
126126
FUN_PY_TRUFFLE_SET_STORAGE_ITEM("PyTruffle_SetStorageItem", ArgDescriptor.Void, Pointer, Int, PyObject),
127127
FUN_PY_TRUFFLE_INITIALIZE_STORAGE_ITEM("PyTruffle_InitializeStorageItem", ArgDescriptor.Void, Pointer, Int, PyObject),
128-
FUN_PY_OBJECT_GENERIC_GET_DICT("_PyObject_GenericGetDict", PyObject, PyObject),
128+
FUN_PY_OBJECT_GET_DICT_PTR("_PyObject_GetDictPtr", Pointer, PyObject),
129129
FUN_PY_OBJECT_GENERIC_SET_DICT("PyObject_GenericSetDict", Int, PyObject, PyObject, Pointer),
130130
FUN_PY_OBJECT_NEW("PyTruffle_Object_New", PyObject, PyTypeObject),
131131
FUN_PY_TYPE_READY("PyType_Ready", Int, PyTypeObject),

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
*/
4747
public abstract class TypeFlags {
4848

49+
public static final long MANAGED_DICT = (1L << 4);
4950
public static final long SEQUENCE = (1L << 5);
5051
public static final long MAPPING = (1L << 6);
5152
public static final long HEAPTYPE = (1L << 9);

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

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
import static com.oracle.graal.python.builtins.objects.type.TypeFlags.IS_ABSTRACT;
7070
import static com.oracle.graal.python.builtins.objects.type.TypeFlags.LIST_SUBCLASS;
7171
import static com.oracle.graal.python.builtins.objects.type.TypeFlags.LONG_SUBCLASS;
72+
import static com.oracle.graal.python.builtins.objects.type.TypeFlags.MANAGED_DICT;
7273
import static com.oracle.graal.python.builtins.objects.type.TypeFlags.MAPPING;
7374
import static com.oracle.graal.python.builtins.objects.type.TypeFlags.MATCH_SELF;
7475
import static com.oracle.graal.python.builtins.objects.type.TypeFlags.METHOD_DESCRIPTOR;
@@ -315,6 +316,10 @@ private long computeFlags(PythonManagedClass clazz) {
315316
result |= IS_ABSTRACT;
316317
}
317318

319+
if ((clazz.getInstanceShape().getFlags() & PythonObject.HAS_SLOTS_BUT_NO_DICT_FLAG) == 0) {
320+
result |= MANAGED_DICT;
321+
}
322+
318323
PythonContext context = PythonContext.get(this);
319324
// flags are inherited
320325
MroSequenceStorage mroStorage = GetMroStorageNode.executeUncached(clazz);
@@ -1390,21 +1395,20 @@ private static boolean extraivars(Object type, Object base, Object typeSlots) {
13901395
return tSize != bSize || tItemSize != bItemSize;
13911396
}
13921397

1393-
if ((GetTypeFlagsNode.executeUncached(type) & HEAPTYPE) != 0) {
1394-
long tDictOffset = GetDictOffsetNode.executeUncached(type);
1395-
long bDictOffset = GetDictOffsetNode.executeUncached(base);
1396-
long tWeakListOffset = GetWeakListOffsetNode.executeUncached(type);
1397-
long bWeakListOffset = GetWeakListOffsetNode.executeUncached(base);
1398-
if (tWeakListOffset != 0 && bWeakListOffset == 0 && tWeakListOffset + SIZEOF_PY_OBJECT_PTR == tSize) {
1399-
tSize -= SIZEOF_PY_OBJECT_PTR;
1400-
}
1401-
if (tDictOffset != 0 && bDictOffset == 0 && tDictOffset + SIZEOF_PY_OBJECT_PTR == tSize) {
1402-
tSize -= SIZEOF_PY_OBJECT_PTR;
1403-
}
1404-
// Check weaklist again in case it precedes dict
1405-
if (tWeakListOffset != 0 && bWeakListOffset == 0 && tWeakListOffset + SIZEOF_PY_OBJECT_PTR == tSize) {
1406-
tSize -= SIZEOF_PY_OBJECT_PTR;
1407-
}
1398+
long flags = GetTypeFlagsNode.executeUncached(type);
1399+
long tDictOffset = GetDictOffsetNode.executeUncached(type);
1400+
long bDictOffset = GetDictOffsetNode.executeUncached(base);
1401+
long tWeakListOffset = GetWeakListOffsetNode.executeUncached(type);
1402+
long bWeakListOffset = GetWeakListOffsetNode.executeUncached(base);
1403+
if (tWeakListOffset != 0 && bWeakListOffset == 0 && tWeakListOffset + SIZEOF_PY_OBJECT_PTR == tSize) {
1404+
tSize -= SIZEOF_PY_OBJECT_PTR;
1405+
}
1406+
if ((flags & MANAGED_DICT) == 0 && tDictOffset != 0 && bDictOffset == 0 && tDictOffset + SIZEOF_PY_OBJECT_PTR == tSize) {
1407+
tSize -= SIZEOF_PY_OBJECT_PTR;
1408+
}
1409+
// Check weaklist again in case it precedes dict
1410+
if (tWeakListOffset != 0 && bWeakListOffset == 0 && tWeakListOffset + SIZEOF_PY_OBJECT_PTR == tSize) {
1411+
tSize -= SIZEOF_PY_OBJECT_PTR;
14081412
}
14091413

14101414
return tSize != bSize;
@@ -2579,6 +2583,8 @@ static void set(Node inliningTarget, PythonManagedClass cls, long value,
25792583
@GenerateInline
25802584
@GenerateCached(false)
25812585
public abstract static class GetDictOffsetNode extends Node {
2586+
private static final long MANAGED_DICT_OFFSET = -8;
2587+
25822588
public abstract long execute(Node inliningTarget, Object cls);
25832589

25842590
public static long executeUncached(Object cls) {
@@ -2587,8 +2593,13 @@ public static long executeUncached(Object cls) {
25872593

25882594
@Specialization
25892595
static long lookup(Object cls,
2596+
@Cached(inline = false) GetTypeFlagsNode getTypeFlagsNode,
25902597
@Cached(inline = false) CExtNodes.LookupNativeI64MemberFromBaseNode lookup) {
2591-
return lookup.execute(cls, PyTypeObject__tp_dictoffset, DICTOFFSET, GetDictOffsetNode::getBuiltinDictoffset);
2598+
long result = lookup.execute(cls, PyTypeObject__tp_dictoffset, DICTOFFSET, GetDictOffsetNode::getBuiltinDictoffset);
2599+
if (result == 0 && (getTypeFlagsNode.execute(cls) & TypeFlags.MANAGED_DICT) != 0) {
2600+
return MANAGED_DICT_OFFSET;
2601+
}
2602+
return result;
25922603
}
25932604

25942605
private static int getBuiltinDictoffset(PythonBuiltinClassType cls) {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetDictIfExistsNode.java

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,13 @@
4141
package com.oracle.graal.python.nodes.object;
4242

4343
import static com.oracle.graal.python.builtins.PythonBuiltinClassType.SystemError;
44-
import static com.oracle.graal.python.builtins.objects.cext.capi.NativeCAPISymbol.FUN_PY_OBJECT_GENERIC_GET_DICT;
44+
import static com.oracle.graal.python.builtins.objects.cext.capi.NativeCAPISymbol.FUN_PY_OBJECT_GET_DICT_PTR;
4545

4646
import com.oracle.graal.python.builtins.objects.PNone;
4747
import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject;
4848
import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes;
49-
import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.NativeToPythonNode;
5049
import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeNode;
50+
import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess;
5151
import com.oracle.graal.python.builtins.objects.dict.PDict;
5252
import com.oracle.graal.python.builtins.objects.module.PythonModule;
5353
import com.oracle.graal.python.builtins.objects.object.PythonObject;
@@ -56,6 +56,7 @@
5656
import com.oracle.graal.python.nodes.HiddenAttr;
5757
import com.oracle.graal.python.nodes.PNodeWithContext;
5858
import com.oracle.graal.python.nodes.PRaiseNode;
59+
import com.oracle.graal.python.runtime.object.PythonObjectFactory;
5960
import com.oracle.truffle.api.CompilerDirectives;
6061
import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff;
6162
import com.oracle.truffle.api.dsl.Bind;
@@ -64,6 +65,8 @@
6465
import com.oracle.truffle.api.dsl.GenerateUncached;
6566
import com.oracle.truffle.api.dsl.Idempotent;
6667
import com.oracle.truffle.api.dsl.Specialization;
68+
import com.oracle.truffle.api.interop.InteropLibrary;
69+
import com.oracle.truffle.api.library.CachedLibrary;
6770
import com.oracle.truffle.api.nodes.Node;
6871
import com.oracle.truffle.api.object.Shape;
6972

@@ -118,17 +121,27 @@ static PDict doPythonObject(PythonObject object,
118121
@Specialization
119122
@InliningCutoff
120123
PDict doNativeObject(PythonAbstractNativeObject object,
121-
@Cached PythonToNativeNode toSulong,
122-
@Cached NativeToPythonNode toJava,
123-
@Cached CExtNodes.PCallCapiFunction callGetDictNode) {
124-
Object javaDict = toJava.execute(callGetDictNode.call(FUN_PY_OBJECT_GENERIC_GET_DICT, toSulong.execute(object)));
125-
if (javaDict instanceof PDict) {
126-
return (PDict) javaDict;
127-
} else if (javaDict == PNone.NO_VALUE) {
124+
@CachedLibrary(limit = "1") InteropLibrary lib,
125+
@Cached PythonToNativeNode toNative,
126+
@Cached CStructAccess.ReadObjectNode readObjectNode,
127+
@Cached CStructAccess.WriteObjectNewRefNode writeObjectNode,
128+
@Cached PythonObjectFactory factory,
129+
@Cached CExtNodes.PCallCapiFunction callGetDictPtr) {
130+
Object dictPtr = callGetDictPtr.call(FUN_PY_OBJECT_GET_DICT_PTR, toNative.execute(object));
131+
if (lib.isNull(dictPtr)) {
128132
return null;
129133
} else {
130-
CompilerDirectives.transferToInterpreterAndInvalidate();
131-
throw PRaiseNode.raiseUncached(this, SystemError, ErrorMessages.DICT_MUST_BE_SET_TO_DICT, javaDict);
134+
Object dictObject = readObjectNode.readGeneric(dictPtr, 0);
135+
if (dictObject == PNone.NO_VALUE) {
136+
PDict dict = factory.createDict();
137+
writeObjectNode.write(dictPtr, dict);
138+
return dict;
139+
} else if (dictObject instanceof PDict dict) {
140+
return dict;
141+
} else {
142+
CompilerDirectives.transferToInterpreterAndInvalidate();
143+
throw PRaiseNode.raiseUncached(this, SystemError, ErrorMessages.DICT_MUST_BE_SET_TO_DICT, dictObject);
144+
}
132145
}
133146
}
134147

0 commit comments

Comments
 (0)