Skip to content

Commit 8319114

Browse files
authored
bpo-45947: Place dict and values pointer at fixed (negative) offset just before GC header. (GH-29879)
* Place __dict__ immediately before GC header for plain Python objects. * Fix up lazy dict creation logic to use managed dict pointers. * Manage values pointer, placing them directly before managed dict pointers. * Convert hint-based load/store attr specialization target managed dict classes. * Specialize LOAD_METHOD for managed dict objects. * Remove unsafe _PyObject_GC_Calloc function. * Remove unsafe _PyObject_GC_Malloc() function. * Add comment explaning use of Py_TPFLAGS_MANAGED_DICT.
1 parent c7e7a4b commit 8319114

19 files changed

+256
-281
lines changed

Include/cpython/object.h

-1
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,6 @@ struct _typeobject {
270270

271271
destructor tp_finalize;
272272
vectorcallfunc tp_vectorcall;
273-
Py_ssize_t tp_inline_values_offset;
274273
};
275274

276275
/* The *real* layout of a type object when allocated on the heap */

Include/cpython/objimpl.h

-3
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,6 @@ PyAPI_FUNC(int) PyObject_IS_GC(PyObject *obj);
9090
# define _PyGC_FINALIZED(o) PyObject_GC_IsFinalized(o)
9191
#endif
9292

93-
PyAPI_FUNC(PyObject *) _PyObject_GC_Malloc(size_t size);
94-
PyAPI_FUNC(PyObject *) _PyObject_GC_Calloc(size_t size);
95-
9693

9794
/* Test if a type supports weak references */
9895
#define PyType_SUPPORTS_WEAKREFS(t) ((t)->tp_weaklistoffset > 0)

Include/internal/pycore_object.h

+22-1
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,15 @@ _PyObject_IS_GC(PyObject *obj)
168168
// Fast inlined version of PyType_IS_GC()
169169
#define _PyType_IS_GC(t) _PyType_HasFeature((t), Py_TPFLAGS_HAVE_GC)
170170

171+
static inline size_t
172+
_PyType_PreHeaderSize(PyTypeObject *tp)
173+
{
174+
return _PyType_IS_GC(tp) * sizeof(PyGC_Head) +
175+
_PyType_HasFeature(tp, Py_TPFLAGS_MANAGED_DICT) * 2 * sizeof(PyObject *);
176+
}
177+
178+
void _PyObject_GC_Link(PyObject *op);
179+
171180
// Usage: assert(_Py_CheckSlotResult(obj, "__getitem__", result != NULL));
172181
extern int _Py_CheckSlotResult(
173182
PyObject *obj,
@@ -185,7 +194,19 @@ extern int _PyObject_StoreInstanceAttribute(PyObject *obj, PyDictValues *values,
185194
PyObject *name, PyObject *value);
186195
PyObject * _PyObject_GetInstanceAttribute(PyObject *obj, PyDictValues *values,
187196
PyObject *name);
188-
PyDictValues ** _PyObject_ValuesPointer(PyObject *);
197+
198+
static inline PyDictValues **_PyObject_ValuesPointer(PyObject *obj)
199+
{
200+
assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
201+
return ((PyDictValues **)obj)-4;
202+
}
203+
204+
static inline PyObject **_PyObject_ManagedDictPointer(PyObject *obj)
205+
{
206+
assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
207+
return ((PyObject **)obj)-3;
208+
}
209+
189210
PyObject ** _PyObject_DictPointer(PyObject *);
190211
int _PyObject_VisitInstanceAttributes(PyObject *self, visitproc visit, void *arg);
191212
void _PyObject_ClearInstanceAttributes(PyObject *self);

Include/object.h

+6
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,12 @@ given type object has a specified feature.
334334

335335
#ifndef Py_LIMITED_API
336336

337+
/* Placement of dict (and values) pointers are managed by the VM, not by the type.
338+
* The VM will automatically set tp_dictoffset. Should not be used for variable sized
339+
* classes, such as classes that extend tuple.
340+
*/
341+
#define Py_TPFLAGS_MANAGED_DICT (1 << 4)
342+
337343
/* Set if instances of the type object are treated as sequences for pattern matching */
338344
#define Py_TPFLAGS_SEQUENCE (1 << 5)
339345
/* Set if instances of the type object are treated as mappings for pattern matching */

Lib/test/test_stable_abi_ctypes.py

-1
Original file line numberDiff line numberDiff line change
@@ -817,7 +817,6 @@ def test_available_symbols(self):
817817
"_PyErr_BadInternalCall",
818818
"_PyObject_CallFunction_SizeT",
819819
"_PyObject_CallMethod_SizeT",
820-
"_PyObject_GC_Malloc",
821820
"_PyObject_GC_New",
822821
"_PyObject_GC_NewVar",
823822
"_PyObject_GC_Resize",

Lib/test/test_sys.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1421,8 +1421,8 @@ def delx(self): del self.__x
14211421
check((1,2,3), vsize('') + 3*self.P)
14221422
# type
14231423
# static type: PyTypeObject
1424-
fmt = 'P2nPI13Pl4Pn9Pn12PIPP'
1425-
s = vsize(fmt)
1424+
fmt = 'P2nPI13Pl4Pn9Pn12PIP'
1425+
s = vsize('2P' + fmt)
14261426
check(int, s)
14271427
# class
14281428
s = vsize(fmt + # PyTypeObject
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Place pointers to dict and values immediately before GC header. This reduces
2+
number of dependent memory loads to access either dict or values from 3 to
3+
1.

Misc/stable_abi.txt

-3
Original file line numberDiff line numberDiff line change
@@ -1577,9 +1577,6 @@ function _PyObject_CallFunction_SizeT
15771577
function _PyObject_CallMethod_SizeT
15781578
added 3.2
15791579
abi_only
1580-
function _PyObject_GC_Malloc
1581-
added 3.2
1582-
abi_only
15831580
function _PyObject_GC_New
15841581
added 3.2
15851582
abi_only

Modules/_testcapimodule.c

+12-5
Original file line numberDiff line numberDiff line change
@@ -5861,6 +5861,7 @@ test_tstate_capi(PyObject *self, PyObject *Py_UNUSED(args))
58615861
}
58625862

58635863

5864+
static PyObject *negative_dictoffset(PyObject *, PyObject *);
58645865
static PyObject *test_buildvalue_issue38913(PyObject *, PyObject *);
58655866
static PyObject *getargs_s_hash_int(PyObject *, PyObject *, PyObject*);
58665867

@@ -5929,14 +5930,15 @@ static PyMethodDef TestMethods[] = {
59295930
#if (defined(__linux__) || defined(__FreeBSD__)) && defined(__GNUC__)
59305931
{"test_pep3118_obsolete_write_locks", (PyCFunction)test_pep3118_obsolete_write_locks, METH_NOARGS},
59315932
#endif
5932-
{"getbuffer_with_null_view", getbuffer_with_null_view, METH_O},
5933-
{"PyBuffer_SizeFromFormat", test_PyBuffer_SizeFromFormat, METH_VARARGS},
5934-
{"test_buildvalue_N", test_buildvalue_N, METH_NOARGS},
5933+
{"getbuffer_with_null_view", getbuffer_with_null_view, METH_O},
5934+
{"PyBuffer_SizeFromFormat", test_PyBuffer_SizeFromFormat, METH_VARARGS},
5935+
{"test_buildvalue_N", test_buildvalue_N, METH_NOARGS},
5936+
{"negative_dictoffset", negative_dictoffset, METH_NOARGS},
59355937
{"test_buildvalue_issue38913", test_buildvalue_issue38913, METH_NOARGS},
5936-
{"get_args", get_args, METH_VARARGS},
5938+
{"get_args", get_args, METH_VARARGS},
59375939
{"test_get_statictype_slots", test_get_statictype_slots, METH_NOARGS},
59385940
{"test_get_type_name", test_get_type_name, METH_NOARGS},
5939-
{"test_get_type_qualname", test_get_type_qualname, METH_NOARGS},
5941+
{"test_get_type_qualname", test_get_type_qualname, METH_NOARGS},
59405942
{"test_type_from_ephemeral_spec", test_type_from_ephemeral_spec, METH_NOARGS},
59415943
{"get_kwargs", (PyCFunction)(void(*)(void))get_kwargs,
59425944
METH_VARARGS|METH_KEYWORDS},
@@ -7629,6 +7631,11 @@ PyInit__testcapi(void)
76297631
return m;
76307632
}
76317633

7634+
static PyObject *
7635+
negative_dictoffset(PyObject *self, PyObject *Py_UNUSED(ignored))
7636+
{
7637+
return PyType_FromSpec(&HeapCTypeWithNegativeDict_spec);
7638+
}
76327639

76337640
/* Test the C API exposed when PY_SSIZE_T_CLEAN is not defined */
76347641

Modules/gcmodule.c

+29-35
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,10 @@ module gc
6969
#define NEXT_MASK_UNREACHABLE (1)
7070

7171
/* Get an object's GC head */
72-
#define AS_GC(o) ((PyGC_Head *)(o)-1)
72+
#define AS_GC(o) ((PyGC_Head *)(((char *)(o))-sizeof(PyGC_Head)))
7373

7474
/* Get the object given the GC head */
75-
#define FROM_GC(g) ((PyObject *)(((PyGC_Head *)g)+1))
75+
#define FROM_GC(g) ((PyObject *)(((char *)(g))+sizeof(PyGC_Head)))
7676

7777
static inline int
7878
gc_is_collecting(PyGC_Head *g)
@@ -2231,28 +2231,14 @@ PyObject_IS_GC(PyObject *obj)
22312231
return _PyObject_IS_GC(obj);
22322232
}
22332233

2234-
static PyObject *
2235-
_PyObject_GC_Alloc(int use_calloc, size_t basicsize)
2234+
void
2235+
_PyObject_GC_Link(PyObject *op)
22362236
{
2237+
PyGC_Head *g = AS_GC(op);
2238+
assert(((uintptr_t)g & (sizeof(uintptr_t)-1)) == 0); // g must be correctly aligned
2239+
22372240
PyThreadState *tstate = _PyThreadState_GET();
22382241
GCState *gcstate = &tstate->interp->gc;
2239-
if (basicsize > PY_SSIZE_T_MAX - sizeof(PyGC_Head)) {
2240-
return _PyErr_NoMemory(tstate);
2241-
}
2242-
size_t size = sizeof(PyGC_Head) + basicsize;
2243-
2244-
PyGC_Head *g;
2245-
if (use_calloc) {
2246-
g = (PyGC_Head *)PyObject_Calloc(1, size);
2247-
}
2248-
else {
2249-
g = (PyGC_Head *)PyObject_Malloc(size);
2250-
}
2251-
if (g == NULL) {
2252-
return _PyErr_NoMemory(tstate);
2253-
}
2254-
assert(((uintptr_t)g & 3) == 0); // g must be aligned 4bytes boundary
2255-
22562242
g->_gc_next = 0;
22572243
g->_gc_prev = 0;
22582244
gcstate->generations[0].count++; /* number of allocated GC objects */
@@ -2266,26 +2252,32 @@ _PyObject_GC_Alloc(int use_calloc, size_t basicsize)
22662252
gc_collect_generations(tstate);
22672253
gcstate->collecting = 0;
22682254
}
2269-
PyObject *op = FROM_GC(g);
2270-
return op;
22712255
}
22722256

2273-
PyObject *
2274-
_PyObject_GC_Malloc(size_t basicsize)
2275-
{
2276-
return _PyObject_GC_Alloc(0, basicsize);
2277-
}
2278-
2279-
PyObject *
2280-
_PyObject_GC_Calloc(size_t basicsize)
2257+
static PyObject *
2258+
gc_alloc(size_t basicsize, size_t presize)
22812259
{
2282-
return _PyObject_GC_Alloc(1, basicsize);
2260+
PyThreadState *tstate = _PyThreadState_GET();
2261+
if (basicsize > PY_SSIZE_T_MAX - presize) {
2262+
return _PyErr_NoMemory(tstate);
2263+
}
2264+
size_t size = presize + basicsize;
2265+
char *mem = PyObject_Malloc(size);
2266+
if (mem == NULL) {
2267+
return _PyErr_NoMemory(tstate);
2268+
}
2269+
((PyObject **)mem)[0] = NULL;
2270+
((PyObject **)mem)[1] = NULL;
2271+
PyObject *op = (PyObject *)(mem + presize);
2272+
_PyObject_GC_Link(op);
2273+
return op;
22832274
}
22842275

22852276
PyObject *
22862277
_PyObject_GC_New(PyTypeObject *tp)
22872278
{
2288-
PyObject *op = _PyObject_GC_Malloc(_PyObject_SIZE(tp));
2279+
size_t presize = _PyType_PreHeaderSize(tp);
2280+
PyObject *op = gc_alloc(_PyObject_SIZE(tp), presize);
22892281
if (op == NULL) {
22902282
return NULL;
22912283
}
@@ -2303,8 +2295,9 @@ _PyObject_GC_NewVar(PyTypeObject *tp, Py_ssize_t nitems)
23032295
PyErr_BadInternalCall();
23042296
return NULL;
23052297
}
2298+
size_t presize = _PyType_PreHeaderSize(tp);
23062299
size = _PyObject_VAR_SIZE(tp, nitems);
2307-
op = (PyVarObject *) _PyObject_GC_Malloc(size);
2300+
op = (PyVarObject *)gc_alloc(size, presize);
23082301
if (op == NULL) {
23092302
return NULL;
23102303
}
@@ -2333,6 +2326,7 @@ _PyObject_GC_Resize(PyVarObject *op, Py_ssize_t nitems)
23332326
void
23342327
PyObject_GC_Del(void *op)
23352328
{
2329+
size_t presize = _PyType_PreHeaderSize(((PyObject *)op)->ob_type);
23362330
PyGC_Head *g = AS_GC(op);
23372331
if (_PyObject_GC_IS_TRACKED(op)) {
23382332
gc_list_remove(g);
@@ -2341,7 +2335,7 @@ PyObject_GC_Del(void *op)
23412335
if (gcstate->generations[0].count > 0) {
23422336
gcstate->generations[0].count--;
23432337
}
2344-
PyObject_Free(g);
2338+
PyObject_Free(((char *)op)-presize);
23452339
}
23462340

23472341
int

0 commit comments

Comments
 (0)