Skip to content

Commit f2b7a25

Browse files
committed
Merge branch 'nogil_dict_pydictvalues-updated' into nogil-integration
2 parents 9dea119 + a34f068 commit f2b7a25

34 files changed

+932
-556
lines changed

Include/cpython/pystats.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ typedef struct _object_stats {
8080
uint64_t dict_materialized_new_key;
8181
uint64_t dict_materialized_too_big;
8282
uint64_t dict_materialized_str_subclass;
83-
uint64_t dict_dematerialized;
8483
uint64_t type_cache_hits;
8584
uint64_t type_cache_misses;
8685
uint64_t type_cache_dunder_hits;

Include/internal/pycore_code.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,10 @@ typedef struct {
7979
typedef struct {
8080
uint16_t counter;
8181
uint16_t type_version[2];
82-
uint16_t keys_version[2];
82+
union {
83+
uint16_t keys_version[2];
84+
uint16_t dict_offset;
85+
};
8386
uint16_t descr[4];
8487
} _PyLoadMethodCache;
8588

Include/internal/pycore_dict.h

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ extern "C" {
99
# error "this header requires Py_BUILD_CORE define"
1010
#endif
1111

12-
#include "pycore_freelist.h" // _PyFreeListState
13-
#include "pycore_identifier.h" // _Py_Identifier
14-
#include "pycore_object.h" // PyDictOrValues
12+
#include "pycore_freelist.h" // _PyFreeListState
13+
#include "pycore_identifier.h" // _Py_Identifier
14+
#include "pycore_object.h" // PyManagedDictPointer
15+
#include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_LOAD_SSIZE_ACQUIRE
1516

1617
// Unsafe flavor of PyDict_GetItemWithError(): no error checking
1718
extern PyObject* _PyDict_GetItemWithError(PyObject *dp, PyObject *key);
@@ -181,6 +182,10 @@ struct _dictkeysobject {
181182
* [-1] = prefix size. [-2] = used size. size[-2-n...] = insertion order.
182183
*/
183184
struct _dictvalues {
185+
uint8_t capacity;
186+
uint8_t size;
187+
uint8_t embedded;
188+
uint8_t valid;
184189
PyObject *values[1];
185190
};
186191

@@ -196,6 +201,7 @@ static inline void* _DK_ENTRIES(PyDictKeysObject *dk) {
196201
size_t index = (size_t)1 << dk->dk_log2_index_bytes;
197202
return (&indices[index]);
198203
}
204+
199205
static inline PyDictKeyEntry* DK_ENTRIES(PyDictKeysObject *dk) {
200206
assert(dk->dk_kind == DICT_KEYS_GENERAL);
201207
return (PyDictKeyEntry*)_DK_ENTRIES(dk);
@@ -211,9 +217,6 @@ static inline PyDictUnicodeEntry* DK_UNICODE_ENTRIES(PyDictKeysObject *dk) {
211217
#define DICT_WATCHER_MASK ((1 << DICT_MAX_WATCHERS) - 1)
212218
#define DICT_WATCHER_AND_MODIFICATION_MASK ((1 << (DICT_MAX_WATCHERS + DICT_WATCHED_MUTATION_BITS)) - 1)
213219

214-
#define DICT_VALUES_SIZE(values) ((uint8_t *)values)[-1]
215-
#define DICT_VALUES_USED_SIZE(values) ((uint8_t *)values)[-2]
216-
217220
#ifdef Py_GIL_DISABLED
218221
#define DICT_NEXT_VERSION(INTERP) \
219222
(_Py_atomic_add_uint64(&(INTERP)->dict_state.global_version, DICT_VERSION_INCREMENT) + DICT_VERSION_INCREMENT)
@@ -246,25 +249,60 @@ _PyDict_NotifyEvent(PyInterpreterState *interp,
246249
return DICT_NEXT_VERSION(interp) | (mp->ma_version_tag & DICT_WATCHER_AND_MODIFICATION_MASK);
247250
}
248251

249-
extern PyObject *_PyObject_MakeDictFromInstanceAttributes(PyObject *obj, PyDictValues *values);
250-
PyAPI_FUNC(bool) _PyObject_MakeInstanceAttributesFromDict(PyObject *obj, PyDictOrValues *dorv);
252+
extern PyObject *_PyObject_MaterializeManagedDict(PyObject *obj);
253+
251254
PyAPI_FUNC(PyObject *)_PyDict_FromItems(
252255
PyObject *const *keys, Py_ssize_t keys_offset,
253256
PyObject *const *values, Py_ssize_t values_offset,
254257
Py_ssize_t length);
255258

259+
static inline uint8_t *
260+
get_insertion_order_array(PyDictValues *values)
261+
{
262+
return (uint8_t *)&values->values[values->capacity];
263+
}
264+
256265
static inline void
257266
_PyDictValues_AddToInsertionOrder(PyDictValues *values, Py_ssize_t ix)
258267
{
259268
assert(ix < SHARED_KEYS_MAX_SIZE);
260-
uint8_t *size_ptr = ((uint8_t *)values)-2;
261-
int size = *size_ptr;
262-
assert(size+2 < DICT_VALUES_SIZE(values));
263-
size++;
264-
size_ptr[-size] = (uint8_t)ix;
265-
*size_ptr = size;
269+
int size = values->size;
270+
uint8_t *array = get_insertion_order_array(values);
271+
assert(size < values->capacity);
272+
assert(((uint8_t)ix) == ix);
273+
array[size] = (uint8_t)ix;
274+
values->size = size+1;
266275
}
267276

277+
static inline size_t
278+
shared_keys_usable_size(PyDictKeysObject *keys)
279+
{
280+
// dk_usable will decrease for each instance that is created and each
281+
// value that is added. dk_nentries will increase for each value that
282+
// is added. We want to always return the right value or larger.
283+
// We therefore increase dk_nentries first and we decrease dk_usable
284+
// second, and conversely here we read dk_usable first and dk_entries
285+
// second (to avoid the case where we read entries before the increment
286+
// and read usable after the decrement)
287+
Py_ssize_t dk_usable = FT_ATOMIC_LOAD_SSIZE_ACQUIRE(keys->dk_usable);
288+
Py_ssize_t dk_nentries = FT_ATOMIC_LOAD_SSIZE_ACQUIRE(keys->dk_nentries);
289+
return dk_nentries + dk_usable;
290+
}
291+
292+
static inline size_t
293+
_PyInlineValuesSize(PyTypeObject *tp)
294+
{
295+
PyDictKeysObject *keys = ((PyHeapTypeObject*)tp)->ht_cached_keys;
296+
assert(keys != NULL);
297+
size_t size = shared_keys_usable_size(keys);
298+
size_t prefix_size = _Py_SIZE_ROUND_UP(size, sizeof(PyObject *));
299+
assert(prefix_size < 256);
300+
return prefix_size + (size + 1) * sizeof(PyObject *);
301+
}
302+
303+
int
304+
_PyDict_DetachFromObject(PyDictObject *dict, PyObject *obj);
305+
268306
#ifdef __cplusplus
269307
}
270308
#endif

Include/internal/pycore_object.h

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ extern "C" {
1212
#include "pycore_gc.h" // _PyObject_GC_IS_TRACKED()
1313
#include "pycore_emscripten_trampoline.h" // _PyCFunction_TrampolineCall()
1414
#include "pycore_interp.h" // PyInterpreterState.gc
15+
#include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_STORE_PTR_RELAXED
1516
#include "pycore_pystate.h" // _PyInterpreterState_GET()
1617

1718
/* Check if an object is consistent. For example, ensure that the reference
@@ -276,9 +277,8 @@ _PyObject_Init(PyObject *op, PyTypeObject *typeobj)
276277
{
277278
assert(op != NULL);
278279
Py_SET_TYPE(op, typeobj);
279-
if (_PyType_HasFeature(typeobj, Py_TPFLAGS_HEAPTYPE)) {
280-
Py_INCREF(typeobj);
281-
}
280+
assert(_PyType_HasFeature(typeobj, Py_TPFLAGS_HEAPTYPE) || _Py_IsImmortal(typeobj));
281+
Py_INCREF(typeobj);
282282
_Py_NewReference(op);
283283
}
284284

@@ -621,12 +621,11 @@ extern PyTypeObject* _PyType_CalculateMetaclass(PyTypeObject *, PyObject *);
621621
extern PyObject* _PyType_GetDocFromInternalDoc(const char *, const char *);
622622
extern PyObject* _PyType_GetTextSignatureFromInternalDoc(const char *, const char *, int);
623623

624-
extern int _PyObject_InitializeDict(PyObject *obj);
625-
int _PyObject_InitInlineValues(PyObject *obj, PyTypeObject *tp);
626-
extern int _PyObject_StoreInstanceAttribute(PyObject *obj, PyDictValues *values,
627-
PyObject *name, PyObject *value);
628-
PyObject * _PyObject_GetInstanceAttribute(PyObject *obj, PyDictValues *values,
629-
PyObject *name);
624+
void _PyObject_InitInlineValues(PyObject *obj, PyTypeObject *tp);
625+
extern int _PyObject_TryStoreInstanceAttribute(PyObject *obj,
626+
PyObject *name, PyObject *value);
627+
extern int _PyObject_TryGetInstanceAttribute(PyObject *obj, PyObject *name,
628+
PyObject **attr);
630629

631630
#ifdef Py_GIL_DISABLED
632631
# define MANAGED_DICT_OFFSET (((Py_ssize_t)sizeof(PyObject *))*-1)
@@ -637,42 +636,36 @@ PyObject * _PyObject_GetInstanceAttribute(PyObject *obj, PyDictValues *values,
637636
#endif
638637

639638
typedef union {
640-
PyObject *dict;
641-
/* Use a char* to generate a warning if directly assigning a PyDictValues */
642-
char *values;
643-
} PyDictOrValues;
639+
PyDictObject *dict;
640+
} PyManagedDictPointer;
644641

645-
static inline PyDictOrValues *
646-
_PyObject_DictOrValuesPointer(PyObject *obj)
642+
static inline PyManagedDictPointer *
643+
_PyObject_ManagedDictPointer(PyObject *obj)
647644
{
648645
assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
649-
return (PyDictOrValues *)((char *)obj + MANAGED_DICT_OFFSET);
650-
}
651-
652-
static inline int
653-
_PyDictOrValues_IsValues(PyDictOrValues dorv)
654-
{
655-
return ((uintptr_t)dorv.values) & 1;
646+
return (PyManagedDictPointer *)((char *)obj + MANAGED_DICT_OFFSET);
656647
}
657648

658-
static inline PyDictValues *
659-
_PyDictOrValues_GetValues(PyDictOrValues dorv)
649+
static inline PyDictObject *
650+
_PyObject_GetManagedDict(PyObject *obj)
660651
{
661-
assert(_PyDictOrValues_IsValues(dorv));
662-
return (PyDictValues *)(dorv.values + 1);
652+
PyManagedDictPointer *dorv = _PyObject_ManagedDictPointer(obj);
653+
return (PyDictObject *)FT_ATOMIC_LOAD_PTR_RELAXED(dorv->dict);
663654
}
664655

665-
static inline PyObject *
666-
_PyDictOrValues_GetDict(PyDictOrValues dorv)
656+
static inline void
657+
_PyObject_SetManagedDict(PyObject *obj, PyObject *dict)
667658
{
668-
assert(!_PyDictOrValues_IsValues(dorv));
669-
return dorv.dict;
659+
PyManagedDictPointer *ptr = _PyObject_ManagedDictPointer(obj);
660+
FT_ATOMIC_STORE_PTR_RELEASE(ptr->dict, dict);
670661
}
671662

672-
static inline void
673-
_PyDictOrValues_SetValues(PyDictOrValues *ptr, PyDictValues *values)
663+
static inline PyDictValues *
664+
_PyObject_InlineValues(PyObject *obj)
674665
{
675-
ptr->values = ((char *)values) - 1;
666+
assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_INLINE_VALUES);
667+
assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
668+
return (PyDictValues *)((char *)obj + sizeof(PyObject));
676669
}
677670

678671
extern PyObject ** _PyObject_ComputedDictPointer(PyObject *);

Include/internal/pycore_opcode_metadata.h

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_pyatomic_ft_wrappers.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,14 @@ extern "C" {
2121

2222
#ifdef Py_GIL_DISABLED
2323
#define FT_ATOMIC_LOAD_SSIZE(value) _Py_atomic_load_ssize(&value)
24+
#define FT_ATOMIC_LOAD_SSIZE_ACQUIRE(value) \
25+
_Py_atomic_load_ssize_acquire(&value)
2426
#define FT_ATOMIC_LOAD_SSIZE_RELAXED(value) \
2527
_Py_atomic_load_ssize_relaxed(&value)
28+
#define FT_ATOMIC_LOAD_PTR_RELAXED(value) \
29+
_Py_atomic_load_ptr_relaxed(&value)
30+
#define FT_ATOMIC_LOAD_UINT8_RELAXED(value) \
31+
_Py_atomic_load_uint8_relaxed(&value)
2632
#define FT_ATOMIC_STORE_PTR_RELAXED(value, new_value) \
2733
_Py_atomic_store_ptr_relaxed(&value, new_value)
2834
#define FT_ATOMIC_STORE_PTR_RELEASE(value, new_value) \
@@ -31,7 +37,10 @@ extern "C" {
3137
_Py_atomic_store_ssize_relaxed(&value, new_value)
3238
#else
3339
#define FT_ATOMIC_LOAD_SSIZE(value) value
40+
#define FT_ATOMIC_LOAD_SSIZE_ACQUIRE(value) value
3441
#define FT_ATOMIC_LOAD_SSIZE_RELAXED(value) value
42+
#define FT_ATOMIC_LOAD_PTR_RELAXED(value) value
43+
#define FT_ATOMIC_LOAD_UINT8_RELAXED(value) value
3544
#define FT_ATOMIC_STORE_PTR_RELAXED(value, new_value) value = new_value
3645
#define FT_ATOMIC_STORE_PTR_RELEASE(value, new_value) value = new_value
3746
#define FT_ATOMIC_STORE_SSIZE_RELAXED(value, new_value) value = new_value

Include/internal/pycore_uop_ids.h

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_uop_metadata.h

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/object.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -626,13 +626,18 @@ given type object has a specified feature.
626626
/* Track types initialized using _PyStaticType_InitBuiltin(). */
627627
#define _Py_TPFLAGS_STATIC_BUILTIN (1 << 1)
628628

629+
/* The values array is placed inline directly after the rest of
630+
* the object. Implies Py_TPFLAGS_HAVE_GC.
631+
*/
632+
#define Py_TPFLAGS_INLINE_VALUES (1 << 2)
633+
629634
/* Placement of weakref pointers are managed by the VM, not by the type.
630635
* The VM will automatically set tp_weaklistoffset.
631636
*/
632637
#define Py_TPFLAGS_MANAGED_WEAKREF (1 << 3)
633638

634639
/* Placement of dict (and values) pointers are managed by the VM, not by the type.
635-
* The VM will automatically set tp_dictoffset.
640+
* The VM will automatically set tp_dictoffset. Implies Py_TPFLAGS_HAVE_GC.
636641
*/
637642
#define Py_TPFLAGS_MANAGED_DICT (1 << 4)
638643

Lib/test/test_capi/test_mem.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,8 @@ class C(): pass
148148
self.assertIn(b'MemoryError', out)
149149
*_, count = line.split(b' ')
150150
count = int(count)
151-
self.assertLessEqual(count, i*5)
152-
self.assertGreaterEqual(count, i*5-2)
151+
self.assertLessEqual(count, i*10)
152+
self.assertGreaterEqual(count, i*10-4)
153153

154154

155155
# Py_GIL_DISABLED requires mimalloc (not malloc)

0 commit comments

Comments
 (0)