@@ -16,7 +16,6 @@ As of Python 3.6, this is compact and ordered. Basic idea is described here:
1616
1717layout:
1818
19- +---------------------+
2019| dk_refcnt |
2120| dk_log2_size |
2221| dk_log2_index_bytes |
@@ -176,8 +175,8 @@ ASSERT_DICT_LOCKED(PyObject *op)
176175
177176#define IS_DICT_SHARED (mp ) _PyObject_GC_IS_SHARED(mp)
178177#define SET_DICT_SHARED (mp ) _PyObject_GC_SET_SHARED(mp)
179- #define LOAD_INDEX (keys , size , idx ) _Py_atomic_load_int##size##_relaxed(&((const int##size##_t*)keys->dk_indices)[ idx]);
180- #define STORE_INDEX (keys , size , idx , value ) _Py_atomic_store_int##size##_relaxed(&((int##size##_t*)keys->dk_indices)[ idx], (int##size##_t)value);
178+ #define LOAD_INDEX (keys , size , idx ) _Py_atomic_load_int##size##_relaxed(&((const int##size##_t*)_DK_INDICES_END( keys))[-1 - ( idx) ]);
179+ #define STORE_INDEX (keys , size , idx , value ) _Py_atomic_store_int##size##_relaxed(&((int##size##_t*)_DK_INDICES_END( keys))[-1 - ( idx) ], (int##size##_t)value);
181180#define ASSERT_OWNED_OR_SHARED (mp ) \
182181 assert(_Py_IsOwnedByCurrentThread((PyObject *)mp) || IS_DICT_SHARED(mp));
183182
@@ -256,8 +255,8 @@ static inline void split_keys_entry_added(PyDictKeysObject *keys)
256255#define UNLOCK_KEYS_IF_SPLIT (keys , kind )
257256#define IS_DICT_SHARED (mp ) (false)
258257#define SET_DICT_SHARED (mp )
259- #define LOAD_INDEX (keys , size , idx ) ((const int##size##_t*)(keys->dk_indices ))[idx]
260- #define STORE_INDEX (keys , size , idx , value ) ((int##size##_t*)(keys->dk_indices ))[idx] = (int##size##_t)value
258+ #define LOAD_INDEX (keys , size , idx ) ((const int##size##_t*)_DK_INDICES_END (keys))[-1 - ( idx) ]
259+ #define STORE_INDEX (keys , size , idx , value ) ((int##size##_t*)_DK_INDICES_END (keys))[-1 - ( idx) ] = (int##size##_t)value
261260
262261static inline void split_keys_entry_added (PyDictKeysObject * keys )
263262{
@@ -513,14 +512,14 @@ dictkeys_get_index(const PyDictKeysObject *keys, Py_ssize_t i)
513512 int log2size = DK_LOG_SIZE (keys );
514513 Py_ssize_t ix ;
515514
516- if (log2size < 8 ) {
515+ if (keys -> dk_log2_index_bytes == log2size ) {
517516 ix = LOAD_INDEX (keys , 8 , i );
518517 }
519- else if (log2size < 16 ) {
518+ else if (keys -> dk_log2_index_bytes == log2size + 1 ) {
520519 ix = LOAD_INDEX (keys , 16 , i );
521520 }
522521#if SIZEOF_VOID_P > 4
523- else if (log2size >= 32 ) {
522+ else if (keys -> dk_log2_index_bytes == log2size + 3 ) {
524523 ix = LOAD_INDEX (keys , 64 , i );
525524 }
526525#endif
@@ -540,16 +539,16 @@ dictkeys_set_index(PyDictKeysObject *keys, Py_ssize_t i, Py_ssize_t ix)
540539 assert (ix >= DKIX_DUMMY );
541540 assert (keys -> dk_version == 0 );
542541
543- if (log2size < 8 ) {
542+ if (keys -> dk_log2_index_bytes == log2size ) {
544543 assert (ix <= 0x7f );
545544 STORE_INDEX (keys , 8 , i , ix );
546545 }
547- else if (log2size < 16 ) {
546+ else if (keys -> dk_log2_index_bytes == log2size + 1 ) {
548547 assert (ix <= 0x7fff );
549548 STORE_INDEX (keys , 16 , i , ix );
550549 }
551550#if SIZEOF_VOID_P > 4
552- else if (log2size >= 32 ) {
551+ else if (keys -> dk_log2_index_bytes == log2size + 3 ) {
553552 STORE_INDEX (keys , 64 , i , ix );
554553 }
555554#endif
@@ -626,7 +625,15 @@ estimate_log2_keysize(Py_ssize_t n)
626625 * See https://github.com/python/cpython/pull/127568#discussion_r1868070614
627626 * for the rationale of using dk_log2_index_bytes=3 instead of 0.
628627 */
629- static PyDictKeysObject empty_keys_struct = {
628+ typedef struct {
629+ int8_t indices [8 ];
630+ PyDictKeysObject keys ;
631+ } _PyDict_EmptyKeysStorage ;
632+
633+ static _PyDict_EmptyKeysStorage empty_keys_storage = {
634+ {DKIX_EMPTY , DKIX_EMPTY , DKIX_EMPTY , DKIX_EMPTY ,
635+ DKIX_EMPTY , DKIX_EMPTY , DKIX_EMPTY , DKIX_EMPTY },
636+ {
630637 _Py_DICT_IMMORTAL_INITIAL_REFCNT , /* dk_refcnt */
631638 0 , /* dk_log2_size */
632639 3 , /* dk_log2_index_bytes */
@@ -637,11 +644,14 @@ static PyDictKeysObject empty_keys_struct = {
637644 1 , /* dk_version */
638645 0 , /* dk_usable (immutable) */
639646 0 , /* dk_nentries */
640- {DKIX_EMPTY , DKIX_EMPTY , DKIX_EMPTY , DKIX_EMPTY ,
641- DKIX_EMPTY , DKIX_EMPTY , DKIX_EMPTY , DKIX_EMPTY }, /* dk_indices */
647+ {} ,
648+ }
642649};
643650
644- #define Py_EMPTY_KEYS &empty_keys_struct
651+ static_assert (offsetof(_PyDict_EmptyKeysStorage , keys ) == 8 ,
652+ "empty_keys_storage layout mismatch" );
653+
654+ #define Py_EMPTY_KEYS (&empty_keys_storage.keys)
645655
646656/* Uncomment to check the dict content in _PyDict_CheckConsistency() */
647657// #define DEBUG_PYDICT
@@ -809,18 +819,27 @@ new_keys_object(uint8_t log2_size, bool unicode)
809819 }
810820
811821 PyDictKeysObject * dk = NULL ;
822+ size_t indices_size = (size_t )1 << log2_bytes ;
823+ void * base = NULL ;
824+
812825 if (log2_size == PyDict_LOG_MINSIZE && unicode ) {
813- dk = _Py_FREELIST_POP_MEM (dictkeys );
826+ base = _Py_FREELIST_POP_MEM (dictkeys );
827+ if (base != NULL ) {
828+ dk = (PyDictKeysObject * )((char * )base + indices_size );
829+ }
814830 }
815- if (dk == NULL ) {
816- dk = PyMem_Malloc (sizeof (PyDictKeysObject )
817- + ((size_t )1 << log2_bytes )
818- + entry_size * usable );
819- if (dk == NULL ) {
831+
832+ if (base == NULL ) {
833+ base = PyMem_Malloc (indices_size
834+ + sizeof (PyDictKeysObject )
835+ + entry_size * usable );
836+ if (base == NULL ) {
820837 PyErr_NoMemory ();
821838 return NULL ;
822839 }
840+ dk = (PyDictKeysObject * )((char * )base + indices_size );
823841 }
842+
824843#ifdef Py_REF_DEBUG
825844 _Py_IncRefTotal (_PyThreadState_GET ());
826845#endif
@@ -834,25 +853,28 @@ new_keys_object(uint8_t log2_size, bool unicode)
834853 dk -> dk_nentries = 0 ;
835854 dk -> dk_usable = usable ;
836855 dk -> dk_version = 0 ;
837- memset (& dk -> dk_indices [ 0 ] , 0xff , (( size_t ) 1 << log2_bytes ) );
838- memset (& dk -> dk_indices [( size_t ) 1 << log2_bytes ], 0 , entry_size * usable );
856+ memset (_DK_INDICES_BASE ( dk ) , 0xff , indices_size );
857+ memset (& dk -> dk_indices [0 ], 0 , entry_size * usable );
839858 return dk ;
840859}
841860
842861static void
843862free_keys_object (PyDictKeysObject * keys , bool use_qsbr )
844863{
864+ void * base = _DK_ALLOC_BASE (keys );
865+
845866#ifdef Py_GIL_DISABLED
846867 if (use_qsbr ) {
847- _PyMem_FreeDelayed (keys , _PyDict_KeysSize (keys ));
868+ _PyMem_FreeDelayed (base , _PyDict_KeysSize (keys ));
848869 return ;
849870 }
850871#endif
872+
851873 if (DK_LOG_SIZE (keys ) == PyDict_LOG_MINSIZE && keys -> dk_kind == DICT_KEYS_UNICODE ) {
852- _Py_FREELIST_FREE (dictkeys , keys , PyMem_Free );
874+ _Py_FREELIST_FREE (dictkeys , base , PyMem_Free );
853875 }
854876 else {
855- PyMem_Free (keys );
877+ PyMem_Free (base );
856878 }
857879}
858880
@@ -950,14 +972,19 @@ clone_combined_dict_keys(PyDictObject *orig)
950972
951973 ASSERT_DICT_LOCKED (orig );
952974
953- size_t keys_size = _PyDict_KeysSize (orig -> ma_keys );
954- PyDictKeysObject * keys = PyMem_Malloc (keys_size );
955- if (keys == NULL ) {
975+ PyDictKeysObject * orig_keys = orig -> ma_keys ;
976+ size_t keys_size = _PyDict_KeysSize (orig_keys );
977+ size_t indices_size = (size_t )1 << orig_keys -> dk_log2_index_bytes ;
978+
979+ void * base = PyMem_Malloc (keys_size );
980+ if (base == NULL ) {
956981 PyErr_NoMemory ();
957982 return NULL ;
958983 }
959984
960- memcpy (keys , orig -> ma_keys , keys_size );
985+ PyDictKeysObject * keys = (PyDictKeysObject * )((char * )base + indices_size );
986+
987+ memcpy (base , _DK_ALLOC_BASE (orig_keys ), keys_size );
961988
962989 /* After copying key/value pairs, we need to incref all
963990 keys and values and they are about to be co-owned by a
0 commit comments