Skip to content

Commit

Permalink
pythonGH-115776: Allow any fixed sized object to have inline values (p…
Browse files Browse the repository at this point in the history
  • Loading branch information
markshannon authored Aug 21, 2024
1 parent 4b7c488 commit a4fd7aa
Show file tree
Hide file tree
Showing 13 changed files with 61 additions and 38 deletions.
5 changes: 3 additions & 2 deletions Include/internal/pycore_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -803,10 +803,11 @@ _PyObject_GetManagedDict(PyObject *obj)
static inline PyDictValues *
_PyObject_InlineValues(PyObject *obj)
{
PyTypeObject *tp = Py_TYPE(obj);
assert(tp->tp_basicsize > 0 && tp->tp_basicsize % sizeof(PyObject *) == 0);
assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_INLINE_VALUES);
assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
assert(Py_TYPE(obj)->tp_basicsize == sizeof(PyObject));
return (PyDictValues *)((char *)obj + sizeof(PyObject));
return (PyDictValues *)((char *)obj + tp->tp_basicsize);
}

extern PyObject ** _PyObject_ComputedDictPointer(PyObject *);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Enables inline values (Python's equivalent of hidden classes) on any class
who's instances are of a fixed size.
4 changes: 4 additions & 0 deletions Objects/object_layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ So the pre-header is these two fields:
If the object has no physical dictionary, then the ``dict_pointer``
is set to `NULL`.

In 3.13 only objects with no additional data could have inline values.
That is, instances of classes with `tp_basicsize == sizeof(PyObject)`.
In 3.14, any object whose class has `tp_itemsize == 0` can have inline values.
In both versions, the inline values starts `tp_basicsize` bytes after the object.

<details>
<summary> 3.12 </summary>
Expand Down
2 changes: 1 addition & 1 deletion Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -8340,7 +8340,7 @@ type_ready_managed_dict(PyTypeObject *type)
return -1;
}
}
if (type->tp_itemsize == 0 && type->tp_basicsize == sizeof(PyObject)) {
if (type->tp_itemsize == 0) {
type->tp_flags |= Py_TPFLAGS_INLINE_VALUES;
}
return 0;
Expand Down
16 changes: 9 additions & 7 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -2012,9 +2012,10 @@ dummy_func(
DEOPT_IF(!_PyObject_InlineValues(owner_o)->valid);
}

split op(_LOAD_ATTR_INSTANCE_VALUE, (index/1, owner -- attr, null if (oparg & 1))) {
split op(_LOAD_ATTR_INSTANCE_VALUE, (offset/1, owner -- attr, null if (oparg & 1))) {
PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner);
PyObject *attr_o = _PyObject_InlineValues(owner_o)->values[index];
PyObject **value_ptr = (PyObject**)(((char *)owner_o) + offset);
PyObject *attr_o = *value_ptr;
DEOPT_IF(attr_o == NULL);
STAT_INC(LOAD_ATTR, hit);
Py_INCREF(attr_o);
Expand Down Expand Up @@ -2196,16 +2197,17 @@ dummy_func(
EXIT_IF(_PyObject_InlineValues(owner_o)->valid == 0);
}

op(_STORE_ATTR_INSTANCE_VALUE, (index/1, value, owner --)) {
op(_STORE_ATTR_INSTANCE_VALUE, (offset/1, value, owner --)) {
PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner);

STAT_INC(STORE_ATTR, hit);
assert(_PyObject_GetManagedDict(owner_o) == NULL);
PyDictValues *values = _PyObject_InlineValues(owner_o);

PyObject *old_value = values->values[index];
values->values[index] = PyStackRef_AsPyObjectSteal(value);
PyObject **value_ptr = (PyObject**)(((char *)owner_o) + offset);
PyObject *old_value = *value_ptr;
*value_ptr = PyStackRef_AsPyObjectSteal(value);
if (old_value == NULL) {
PyDictValues *values = _PyObject_InlineValues(owner_o);
int index = value_ptr - values->values;
_PyDictValues_AddToInsertionOrder(values, index);
}
else {
Expand Down
20 changes: 12 additions & 8 deletions Python/executor_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Python/gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -2055,6 +2055,9 @@ _PyObject_GC_New(PyTypeObject *tp)
return NULL;
}
_PyObject_Init(op, tp);
if (tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) {
_PyObject_InitInlineValues(op, tp);
}
return op;
}

Expand Down
3 changes: 3 additions & 0 deletions Python/gc_free_threading.c
Original file line number Diff line number Diff line change
Expand Up @@ -1810,6 +1810,9 @@ _PyObject_GC_New(PyTypeObject *tp)
return NULL;
}
_PyObject_Init(op, tp);
if (tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) {
_PyObject_InitInlineValues(op, tp);
}
return op;
}

Expand Down
15 changes: 9 additions & 6 deletions Python/generated_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Python/optimizer_bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -452,10 +452,10 @@ dummy_func(void) {
top, unused[oparg-2], bottom)) {
}

op(_LOAD_ATTR_INSTANCE_VALUE, (index/1, owner -- attr, null if (oparg & 1))) {
op(_LOAD_ATTR_INSTANCE_VALUE, (offset/1, owner -- attr, null if (oparg & 1))) {
attr = sym_new_not_null(ctx);
null = sym_new_null(ctx);
(void)index;
(void)offset;
(void)owner;
}

Expand Down
4 changes: 2 additions & 2 deletions Python/optimizer_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 10 additions & 6 deletions Python/specialize.c
Original file line number Diff line number Diff line change
Expand Up @@ -849,15 +849,19 @@ specialize_dict_access(
assert(PyUnicode_CheckExact(name));
Py_ssize_t index = _PyDictKeys_StringLookup(keys, name);
assert (index != DKIX_ERROR);
if (index != (uint16_t)index) {
SPECIALIZATION_FAIL(base_op,
index == DKIX_EMPTY ?
SPEC_FAIL_ATTR_NOT_IN_KEYS :
SPEC_FAIL_OUT_OF_RANGE);
if (index == DKIX_EMPTY) {
SPECIALIZATION_FAIL(base_op, SPEC_FAIL_ATTR_NOT_IN_KEYS);
return 0;
}
assert(index >= 0);
char *value_addr = (char *)&_PyObject_InlineValues(owner)->values[index];
Py_ssize_t offset = value_addr - (char *)owner;
if (offset != (uint16_t)offset) {
SPECIALIZATION_FAIL(base_op, SPEC_FAIL_OUT_OF_RANGE);
return 0;
}
write_u32(cache->version, type->tp_version_tag);
cache->index = (uint16_t)index;
cache->index = (uint16_t)offset;
instr->op.code = values_op;
}
else {
Expand Down
5 changes: 1 addition & 4 deletions Tools/gdb/libpython.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,6 @@ def _type_unsigned_int_ptr():
def _sizeof_void_p():
return gdb.lookup_type('void').pointer().sizeof

def _sizeof_pyobject():
return gdb.lookup_type('PyObject').sizeof

def _managed_dict_offset():
# See pycore_object.h
pyobj = gdb.lookup_type("PyObject")
Expand Down Expand Up @@ -505,7 +502,7 @@ def get_keys_values(self):
dict_ptr = dict_ptr_ptr.cast(_type_char_ptr().pointer()).dereference()
if int(dict_ptr):
return None
char_ptr = obj_ptr + _sizeof_pyobject()
char_ptr = obj_ptr + typeobj.field('tp_basicsize')
values_ptr = char_ptr.cast(gdb.lookup_type("PyDictValues").pointer())
values = values_ptr['values']
return PyKeysValuesPair(self.get_cached_keys(), values)
Expand Down

0 comments on commit a4fd7aa

Please sign in to comment.