Nuitka
The Python compiler
Loading...
Searching...
No Matches
dictionaries.h
1// Copyright 2026, Kay Hayen, mailto:kay.hayen@gmail.com find license text at end of file
2
3#ifndef __NUITKA_DICTIONARIES_H__
4#define __NUITKA_DICTIONARIES_H__
5
6static inline Py_ssize_t DICT_SIZE(PyObject *dict) {
7 CHECK_OBJECT(dict);
8 assert(PyDict_CheckExact(dict));
9
10 return ((PyDictObject *)dict)->ma_used;
11}
12
13static inline PyDictObject *MODULE_DICT(PyObject *module) {
14 CHECK_OBJECT(module);
15
16 PyDictObject *dict = (PyDictObject *)(((PyModuleObject *)module)->md_dict);
17
18 return dict;
19}
20
21#if PYTHON_VERSION < 0x300
22// Quick dictionary lookup for a string value.
23
24typedef PyDictEntry *Nuitka_DictEntryHandle;
25
26static PyDictEntry *GET_STRING_DICT_ENTRY(PyDictObject *dict, Nuitka_StringObject *key) {
27 assert(PyDict_CheckExact(dict));
28 assert(Nuitka_String_CheckExact(key));
29
30 Py_hash_t hash = key->ob_shash;
31
32 // Only improvement would be to identify how to ensure that the hash is
33 // computed already. Calling hash early on could do that potentially.
34 if (unlikely(hash == -1)) {
35 hash = PyString_Type.tp_hash((PyObject *)key);
36 key->ob_shash = hash;
37 }
38
39 PyDictEntry *entry = dict->ma_lookup(dict, (PyObject *)key, hash);
40
41 // The "entry" cannot be NULL, it can only be empty for a string dict
42 // lookup, but at least assert it.
43 assert(entry != NULL);
44
45 return entry;
46}
47
48NUITKA_MAY_BE_UNUSED static PyObject *GET_DICT_ENTRY_VALUE(Nuitka_DictEntryHandle handle) { return handle->me_value; }
49
50NUITKA_MAY_BE_UNUSED static void SET_DICT_ENTRY_VALUE(Nuitka_DictEntryHandle handle, PyObject *value) {
51 handle->me_value = value;
52}
53
54static PyObject *GET_STRING_DICT_VALUE(PyDictObject *dict, Nuitka_StringObject *key) {
55 return GET_STRING_DICT_ENTRY(dict, key)->me_value;
56}
57
58#else
59
60// Python3
61
62// Quick dictionary lookup for a string value.
63
64#if PYTHON_VERSION < 0x3b0
65typedef struct {
66 /* Cached hash code of me_key. */
67 Py_hash_t me_hash;
68 PyObject *me_key;
69 PyObject *me_value; /* This field is only meaningful for combined tables */
70} PyDictKeyEntry;
71#endif
72
73#if PYTHON_VERSION < 0x360
74typedef PyDictKeyEntry *(*dict_lookup_func)(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject ***value_addr);
75#elif PYTHON_VERSION < 0x370
76typedef Py_ssize_t (*dict_lookup_func)(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject ***value_addr,
77 Py_ssize_t *hashpos);
78#else
79typedef Py_ssize_t (*dict_lookup_func)(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **value_addr);
80#endif
81
82// Taken from CPython3.3 "Objects/dictobject.c", lives in "Objects/dict-common.h" later,
83
84#if PYTHON_VERSION < 0x3b0
85
86#define DK_SIZE(dk) ((dk)->dk_size)
87struct _dictkeysobject { // spell-checker: ignore dictkeysobject
88 Py_ssize_t dk_refcnt;
89 Py_ssize_t dk_size;
90 dict_lookup_func dk_lookup;
91 Py_ssize_t dk_usable;
92#if PYTHON_VERSION < 0x360
93 PyDictKeyEntry dk_entries[1];
94#else
95 Py_ssize_t dk_nentries;
96#if PYTHON_VERSION < 0x370
97 union {
98 int8_t as_1[8];
99 int16_t as_2[4];
100 int32_t as_4[2];
101#if SIZEOF_VOID_P > 4
102 int64_t as_8[1];
103#endif
104 } dk_indices;
105#else
106 char dk_indices[];
107#endif
108#endif
109};
110
111#endif
112
113// Taken from Objects/dictobject.c of CPython 3.6
114#if PYTHON_VERSION >= 0x360
115
116#if PYTHON_VERSION < 0x3b0
117#if SIZEOF_VOID_P > 4
118// spell-checker: ignore DK_IXSIZE
119#define DK_IXSIZE(dk) \
120 (DK_SIZE(dk) <= 0xff ? 1 : DK_SIZE(dk) <= 0xffff ? 2 : DK_SIZE(dk) <= 0xffffffff ? 4 : sizeof(int64_t))
121#else
122#define DK_IXSIZE(dk) (DK_SIZE(dk) <= 0xff ? 1 : DK_SIZE(dk) <= 0xffff ? 2 : sizeof(int32_t))
123#endif
124
125#if PYTHON_VERSION < 0x370
126#define DK_ENTRIES(dk) ((PyDictKeyEntry *)(&(dk)->dk_indices.as_1[DK_SIZE(dk) * DK_IXSIZE(dk)]))
127#else
128#define DK_ENTRIES(dk) ((PyDictKeyEntry *)(&((int8_t *)((dk)->dk_indices))[DK_SIZE(dk) * DK_IXSIZE(dk)]))
129#endif
130
131#endif
132
133#else
134#define DK_ENTRIES(dk) (dk->dk_entries)
135#endif
136
137#if PYTHON_VERSION < 0x3b0
138#define DK_VALUE(dk, i) dk->ma_values[i]
139#else
140#define DK_VALUE(dk, i) dk->ma_values->values[i]
141#endif
142
143#define DK_MASK(dk) (DK_SIZE(dk) - 1)
144
145typedef PyObject **Nuitka_DictEntryHandle;
146
147#if PYTHON_VERSION >= 0x3b0
148extern Py_ssize_t Nuitka_Py_unicodekeys_lookup_unicode(PyDictKeysObject *dk, PyObject *key, Py_hash_t hash);
149
150extern Py_ssize_t Nuitka_PyDictLookup(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject ***value_addr);
151extern Py_ssize_t Nuitka_PyDictLookupStr(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject ***value_addr);
152#endif
153
154static inline Py_hash_t Nuitka_Py_unicode_get_hash(PyObject *o) {
155 assert(PyUnicode_CheckExact(o));
156
157 return FT_ATOMIC_LOAD_SSIZE_RELAXED(((PyUnicodeObject *)o)->_base._base.hash);
158}
159
160static Nuitka_DictEntryHandle GET_STRING_DICT_ENTRY(PyDictObject *dict, Nuitka_StringObject *key) {
161 assert(PyDict_CheckExact(dict));
162 assert(Nuitka_String_CheckExact(key));
163
164 Py_hash_t hash = Nuitka_Py_unicode_get_hash((PyObject *)key);
165
166 // Only improvement would be to identify how to ensure that the hash is computed
167 // already. Calling hash early on could do that potentially.
168 if (hash == -1) {
169 hash = PyUnicode_Type.tp_hash((PyObject *)key);
170 }
171
172#if PYTHON_VERSION < 0x360
173 PyObject **value_addr;
174
175 PyDictKeyEntry *entry = dict->ma_keys->dk_lookup(dict, (PyObject *)key, hash, &value_addr);
176
177 // The "entry" cannot be NULL, it can only be empty for a string dict lookup, but at
178 // least assert it.
179 assert(entry != NULL);
180
181 return value_addr;
182
183#elif PYTHON_VERSION < 0x370
184 PyObject **value_addr;
185
186 // TODO: Find out what the returned Py_ssize_t "ix" might be good for.
187 dict->ma_keys->dk_lookup(dict, (PyObject *)key, hash, &value_addr,
188 NULL // hashpos, TODO: Find out what we could use it for.
189 );
190
191 return value_addr;
192#elif PYTHON_VERSION < 0x3b0
193 PyObject *value;
194
195 Py_ssize_t ix = dict->ma_keys->dk_lookup(dict, (PyObject *)key, hash, &value);
196
197 if (value == NULL) {
198 return NULL;
199 } else if (_PyDict_HasSplitTable(dict)) {
200 return &dict->ma_values[ix];
201 } else {
202 return &DK_ENTRIES(dict->ma_keys)[ix].me_value;
203 }
204
205#else
206 PyObject **value;
207 NUITKA_MAY_BE_UNUSED Py_ssize_t found;
208
209 // We cannot use Nuitka_PyDictLookupStr in all cases, some modules misbehave
210 // and put non-strings in module dictionaries.
211 if (unlikely(dict->ma_keys->dk_kind == DICT_KEYS_GENERAL)) {
212 found = Nuitka_PyDictLookup(dict, (PyObject *)key, hash, &value);
213 } else {
214 found = Nuitka_PyDictLookupStr(dict, (PyObject *)key, hash, &value);
215 }
216
217 assert(found != DKIX_ERROR); // spell-checker: ignore DKIX_ERROR
218
219 return value;
220#endif
221}
222
223NUITKA_MAY_BE_UNUSED static PyObject *GET_DICT_ENTRY_VALUE(Nuitka_DictEntryHandle handle) { return *handle; }
224
225NUITKA_MAY_BE_UNUSED static void SET_DICT_ENTRY_VALUE(Nuitka_DictEntryHandle handle, PyObject *value) {
226 *handle = value;
227}
228
229NUITKA_MAY_BE_UNUSED static PyObject *GET_STRING_DICT_VALUE(PyDictObject *dict, Nuitka_StringObject *key) {
230 Nuitka_DictEntryHandle handle = GET_STRING_DICT_ENTRY(dict, key);
231
232#if PYTHON_VERSION >= 0x360
233 if (handle == NULL) {
234 return NULL;
235 }
236#endif
237
238 return GET_DICT_ENTRY_VALUE(handle);
239}
240
241#endif
242
243NUITKA_MAY_BE_UNUSED static bool DICT_SET_ITEM(PyObject *dict, PyObject *key, PyObject *value) {
244 CHECK_OBJECT(dict);
245 assert(PyDict_Check(dict));
246 CHECK_OBJECT(key);
247 CHECK_OBJECT(value);
248
249 int status = PyDict_SetItem(dict, key, value);
250
251 if (unlikely(status != 0)) {
252 return false;
253 }
254
255 return true;
256}
257
258NUITKA_MAY_BE_UNUSED static bool DICT_REMOVE_ITEM(PyObject *dict, PyObject *key) {
259 int status = PyDict_DelItem(dict, key);
260
261 if (unlikely(status == -1)) {
262 return false;
263 }
264
265 return true;
266}
267
268// Get dict lookup for a key, similar to PyDict_GetItemWithError, ref returned
269extern PyObject *DICT_GET_ITEM_WITH_ERROR(PyThreadState *tstate, PyObject *dict, PyObject *key);
270
271// Get dict lookup for a key, with only hash error, does not create KeyError, 1=ref returned, 0=not
272extern PyObject *DICT_GET_ITEM_WITH_HASH_ERROR1(PyThreadState *tstate, PyObject *dict, PyObject *key);
273extern PyObject *DICT_GET_ITEM_WITH_HASH_ERROR0(PyThreadState *tstate, PyObject *dict, PyObject *key);
274
275// Get dict lookup for a key, similar to PyDict_GetItem, 1=ref returned, 0=not
276extern PyObject *DICT_GET_ITEM1(PyThreadState *tstate, PyObject *dict, PyObject *key);
277extern PyObject *DICT_GET_ITEM0(PyThreadState *tstate, PyObject *dict, PyObject *key);
278
279// Get dict lookup for a key, similar to PyDict_Contains
280extern int DICT_HAS_ITEM(PyThreadState *tstate, PyObject *dict, PyObject *key);
281
282// Convert to dictionary, helper for built-in "dict" mainly.
283extern PyObject *TO_DICT(PyThreadState *tstate, PyObject *seq_obj, PyObject *dict_obj);
284
285// For 3.6 to 3.10, the dictionary version tag is used for caching purposes, so
286// we need to maintain it. However it's private value so we have to play a game
287// of having our own number space inside of their 64 bit numbers.
288#if PYTHON_VERSION >= 0x360 && PYTHON_VERSION <= 0x3c0
289#define _NUITKA_MAINTAIN_DICT_VERSION_TAG 1
290#endif
291
292#if _NUITKA_MAINTAIN_DICT_VERSION_TAG
293extern uint64_t nuitka_dict_version_tag_counter;
294#endif
295
296NUITKA_MAY_BE_UNUSED static void UPDATE_STRING_DICT0(PyDictObject *dict, Nuitka_StringObject *key, PyObject *value) {
297 CHECK_OBJECT(value);
298
299 Nuitka_DictEntryHandle entry = GET_STRING_DICT_ENTRY(dict, key);
300
301#if PYTHON_VERSION >= 0x360
302 if (entry == NULL) {
303 DICT_SET_ITEM((PyObject *)dict, (PyObject *)key, value);
304 return;
305 }
306#endif
307
308 PyObject *old = GET_DICT_ENTRY_VALUE(entry);
309 CHECK_OBJECT_X(old);
310
311 if (value != old) {
312
313 // Values are more likely (more often) set than not set, in that case
314 // speculatively try the quickest access method.
315 if (likely(old != NULL)) {
316 Py_INCREF(value);
317 SET_DICT_ENTRY_VALUE(entry, value);
318
319#if _NUITKA_MAINTAIN_DICT_VERSION_TAG
320 dict->ma_version_tag = nuitka_dict_version_tag_counter++;
321#endif
322 CHECK_OBJECT(old);
323
324 Py_DECREF(old);
325 } else {
326 DICT_SET_ITEM((PyObject *)dict, (PyObject *)key, value);
327 }
328 }
329}
330
331NUITKA_MAY_BE_UNUSED static void UPDATE_STRING_DICT_INPLACE(PyDictObject *dict, Nuitka_StringObject *key,
332 PyObject *value) {
333 CHECK_OBJECT(value);
334
335 Nuitka_DictEntryHandle entry = GET_STRING_DICT_ENTRY(dict, key);
336
337#if PYTHON_VERSION >= 0x360
338 if (entry == NULL) {
339 DICT_SET_ITEM((PyObject *)dict, (PyObject *)key, value);
340
341 Py_DECREF(value);
342 CHECK_OBJECT(value);
343
344 return;
345 }
346#endif
347
348 PyObject *old = GET_DICT_ENTRY_VALUE(entry);
349
350 if (old != value) {
351 // Values are more likely (more often) set than not set, in that case
352 // speculatively try the quickest access method.
353 if (likely(old != NULL)) {
354 SET_DICT_ENTRY_VALUE(entry, value);
355
356#if _NUITKA_MAINTAIN_DICT_VERSION_TAG
357 dict->ma_version_tag = nuitka_dict_version_tag_counter++;
358#endif
359 } else {
360 DICT_SET_ITEM((PyObject *)dict, (PyObject *)key, value);
361 Py_DECREF(value);
362
363 CHECK_OBJECT(value);
364 }
365 } else {
366 Py_DECREF(value);
367 CHECK_OBJECT(value);
368 }
369}
370
371NUITKA_MAY_BE_UNUSED static void UPDATE_STRING_DICT1(PyDictObject *dict, Nuitka_StringObject *key, PyObject *value) {
372 CHECK_OBJECT(key);
373 CHECK_OBJECT(value);
374
375 Nuitka_DictEntryHandle entry = GET_STRING_DICT_ENTRY(dict, key);
376
377#if PYTHON_VERSION >= 0x360
378 if (entry == NULL) {
379 DICT_SET_ITEM((PyObject *)dict, (PyObject *)key, value);
380
381 Py_DECREF(value);
382 CHECK_OBJECT(value);
383
384 return;
385 }
386#endif
387
388 PyObject *old = GET_DICT_ENTRY_VALUE(entry);
389 CHECK_OBJECT_X(old);
390
391 if (old != value) {
392 // Values are more likely (more often) set than not set, in that case
393 // speculatively try the quickest access method.
394 if (likely(old != NULL)) {
395 SET_DICT_ENTRY_VALUE(entry, value);
396
397#if _NUITKA_MAINTAIN_DICT_VERSION_TAG
398 dict->ma_version_tag = nuitka_dict_version_tag_counter++;
399#endif
400 Py_DECREF(old);
401 } else {
402 DICT_SET_ITEM((PyObject *)dict, (PyObject *)key, value);
403
404 Py_DECREF(value);
405 CHECK_OBJECT(value);
406 }
407 } else {
408 Py_DECREF(value);
409 CHECK_OBJECT(value);
410 }
411}
412
413#if PYTHON_VERSION < 0x300
414// Python2 dictionary keys, return a list of keys
415extern PyObject *DICT_KEYS(PyObject *dict);
416// Python2 dictionary items, return a list of values
417extern PyObject *DICT_VALUES(PyObject *dict);
418// Python2 dictionary items, return a list of key/value tuples
419extern PyObject *DICT_ITEMS(PyObject *dict);
420#endif
421
422// Python3 dictionary keys, Python2 iterkeys returns dictionary keys iterator
423extern PyObject *DICT_ITERKEYS(PyThreadState *tstate, PyObject *dict);
424
425// Python3 dictionary values, Python2 itervalues returns dictionary values iterator
426extern PyObject *DICT_ITERVALUES(PyThreadState *tstate, PyObject *dict);
427
428// Python3 dictionary items, Python2 iteritems returns dictionary items iterator
429extern PyObject *DICT_ITERITEMS(PyThreadState *tstate, PyObject *dict);
430
431// Python dictionary keys view
432extern PyObject *DICT_VIEWKEYS(PyObject *dict);
433
434// Python dictionary values view
435extern PyObject *DICT_VIEWVALUES(PyObject *dict);
436
437// Python dictionary items view
438extern PyObject *DICT_VIEWITEMS(PyObject *dict);
439
440// Python dictionary copy, return a shallow copy of a dictionary.
441extern PyObject *DICT_COPY(PyThreadState *tstate, PyObject *dict);
442
443// Python dictionary clear, empties the dictionary.
444extern void DICT_CLEAR(PyObject *dict);
445
446// Replacement for PyDict_Next that is faster (to call).
447extern bool Nuitka_DictNext(PyObject *dict, Py_ssize_t *pos, PyObject **key_ptr, PyObject **value_ptr);
448
449#if PYTHON_VERSION >= 0x3a0 && !defined(_NUITKA_EXPERIMENTAL_DISABLE_FREELIST_ALL) && \
450 !defined(_NUITKA_EXPERIMENTAL_DISABLE_FREELIST_DICT)
451#define NUITKA_DICT_HAS_FREELIST 1
452
453// Replacement for PyDict_New that is faster
454extern PyObject *MAKE_DICT_EMPTY(PyThreadState *tstate);
455#else
456#define NUITKA_DICT_HAS_FREELIST 0
457static inline PyObject *MAKE_DICT_EMPTY(PyThreadState *tstate) {
458 (void)tstate;
459
460 PyObject *result = PyDict_New();
461 assert(result != NULL);
462 return result;
463}
464#endif
465
466// Create a dictionary from key/value pairs.
467extern PyObject *MAKE_DICT(PyObject **pairs, Py_ssize_t size);
468// Create a dictionary from key/value pairs (NULL value means skip)
469extern PyObject *MAKE_DICT_X(PyObject **pairs, Py_ssize_t size);
470// Create a dictionary from key/value pairs (NULL value means skip) where keys are C strings.
471extern PyObject *MAKE_DICT_X_CSTR(char const **keys, PyObject **values, Py_ssize_t size);
472
473// TODO: It's probably unchanged across all Python versions 2.6-3.14
474#if PYTHON_VERSION >= 0x3e0
475typedef struct {
476 PyObject_HEAD PyDictObject *di_dict;
477 Py_ssize_t di_used;
478 Py_ssize_t di_pos;
479 PyObject *di_result;
480 Py_ssize_t len;
482#endif
483
484#endif
485
486// Part of "Nuitka", an optimizing Python compiler that is compatible and
487// integrates with CPython, but also works on its own.
488//
489// Licensed under the GNU Affero General Public License, Version 3 (the "License");
490// you may not use this file except in compliance with the License.
491// You may obtain a copy of the License at
492//
493// http://www.gnu.org/licenses/agpl.txt
494//
495// Unless required by applicable law or agreed to in writing, software
496// distributed under the License is distributed on an "AS IS" BASIS,
497// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
498// See the License for the specific language governing permissions and
499// limitations under the License.
Definition helpers.h:10
Definition HelpersDictionaries.c:904