Nuitka
The Python compiler
Loading...
Searching...
No Matches
dictionaries.h
1// Copyright 2025, 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 {
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#define DK_IXSIZE(dk) \
119 (DK_SIZE(dk) <= 0xff ? 1 : DK_SIZE(dk) <= 0xffff ? 2 : DK_SIZE(dk) <= 0xffffffff ? 4 : sizeof(int64_t))
120#else
121#define DK_IXSIZE(dk) (DK_SIZE(dk) <= 0xff ? 1 : DK_SIZE(dk) <= 0xffff ? 2 : sizeof(int32_t))
122#endif
123
124#if PYTHON_VERSION < 0x370
125#define DK_ENTRIES(dk) ((PyDictKeyEntry *)(&(dk)->dk_indices.as_1[DK_SIZE(dk) * DK_IXSIZE(dk)]))
126#else
127#define DK_ENTRIES(dk) ((PyDictKeyEntry *)(&((int8_t *)((dk)->dk_indices))[DK_SIZE(dk) * DK_IXSIZE(dk)]))
128#endif
129
130#endif
131
132#else
133#define DK_ENTRIES(dk) (dk->dk_entries)
134#endif
135
136#if PYTHON_VERSION < 0x3b0
137#define DK_VALUE(dk, i) dk->ma_values[i]
138#else
139#define DK_VALUE(dk, i) dk->ma_values->values[i]
140#endif
141
142#define DK_MASK(dk) (DK_SIZE(dk) - 1)
143
144typedef PyObject **Nuitka_DictEntryHandle;
145
146#if PYTHON_VERSION >= 0x3b0
147extern Py_ssize_t Nuitka_Py_unicodekeys_lookup_unicode(PyDictKeysObject *dk, PyObject *key, Py_hash_t hash);
148
149extern Py_ssize_t Nuitka_PyDictLookup(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject ***value_addr);
150extern Py_ssize_t Nuitka_PyDictLookupStr(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject ***value_addr);
151#endif
152
153static inline Py_hash_t Nuitka_Py_unicode_get_hash(PyObject *o) {
154 assert(PyUnicode_CheckExact(o));
155
156 return FT_ATOMIC_LOAD_SSIZE_RELAXED(((PyUnicodeObject *)o)->_base._base.hash);
157}
158
159static Nuitka_DictEntryHandle GET_STRING_DICT_ENTRY(PyDictObject *dict, Nuitka_StringObject *key) {
160 assert(PyDict_CheckExact(dict));
161 assert(Nuitka_String_CheckExact(key));
162
163 Py_hash_t hash = Nuitka_Py_unicode_get_hash((PyObject *)key);
164
165 // Only improvement would be to identify how to ensure that the hash is computed
166 // already. Calling hash early on could do that potentially.
167 if (hash == -1) {
168 hash = PyUnicode_Type.tp_hash((PyObject *)key);
169 }
170
171#if PYTHON_VERSION < 0x360
172 PyObject **value_addr;
173
174 PyDictKeyEntry *entry = dict->ma_keys->dk_lookup(dict, (PyObject *)key, hash, &value_addr);
175
176 // The "entry" cannot be NULL, it can only be empty for a string dict lookup, but at
177 // least assert it.
178 assert(entry != NULL);
179
180 return value_addr;
181
182#elif PYTHON_VERSION < 0x370
183 PyObject **value_addr;
184
185 // TODO: Find out what the returned Py_ssize_t "ix" might be good for.
186 dict->ma_keys->dk_lookup(dict, (PyObject *)key, hash, &value_addr,
187 NULL // hashpos, TODO: Find out what we could use it for.
188 );
189
190 return value_addr;
191#elif PYTHON_VERSION < 0x3b0
192 PyObject *value;
193
194 Py_ssize_t ix = dict->ma_keys->dk_lookup(dict, (PyObject *)key, hash, &value);
195
196 if (value == NULL) {
197 return NULL;
198 } else if (_PyDict_HasSplitTable(dict)) {
199 return &dict->ma_values[ix];
200 } else {
201 return &DK_ENTRIES(dict->ma_keys)[ix].me_value;
202 }
203
204#else
205 PyObject **value;
206 NUITKA_MAY_BE_UNUSED Py_ssize_t found;
207
208 // We cannot use Nuitka_PyDictLookupStr in all cases, some modules misbehave
209 // and put non-strings in module dictionaries.
210 if (unlikely(dict->ma_keys->dk_kind == DICT_KEYS_GENERAL)) {
211 found = Nuitka_PyDictLookup(dict, (PyObject *)key, hash, &value);
212 } else {
213 found = Nuitka_PyDictLookupStr(dict, (PyObject *)key, hash, &value);
214 }
215
216 assert(found != DKIX_ERROR);
217
218 return value;
219#endif
220}
221
222NUITKA_MAY_BE_UNUSED static PyObject *GET_DICT_ENTRY_VALUE(Nuitka_DictEntryHandle handle) { return *handle; }
223
224NUITKA_MAY_BE_UNUSED static void SET_DICT_ENTRY_VALUE(Nuitka_DictEntryHandle handle, PyObject *value) {
225 *handle = value;
226}
227
228NUITKA_MAY_BE_UNUSED static PyObject *GET_STRING_DICT_VALUE(PyDictObject *dict, Nuitka_StringObject *key) {
229 Nuitka_DictEntryHandle handle = GET_STRING_DICT_ENTRY(dict, key);
230
231#if PYTHON_VERSION >= 0x360
232 if (handle == NULL) {
233 return NULL;
234 }
235#endif
236
237 return GET_DICT_ENTRY_VALUE(handle);
238}
239
240#endif
241
242NUITKA_MAY_BE_UNUSED static bool DICT_SET_ITEM(PyObject *dict, PyObject *key, PyObject *value) {
243 CHECK_OBJECT(dict);
244 assert(PyDict_Check(dict));
245 CHECK_OBJECT(key);
246 CHECK_OBJECT(value);
247
248 int status = PyDict_SetItem(dict, key, value);
249
250 if (unlikely(status != 0)) {
251 return false;
252 }
253
254 return true;
255}
256
257NUITKA_MAY_BE_UNUSED static bool DICT_REMOVE_ITEM(PyObject *dict, PyObject *key) {
258 int status = PyDict_DelItem(dict, key);
259
260 if (unlikely(status == -1)) {
261 return false;
262 }
263
264 return true;
265}
266
267// Get dict lookup for a key, similar to PyDict_GetItemWithError, ref returned
268extern PyObject *DICT_GET_ITEM_WITH_ERROR(PyThreadState *tstate, PyObject *dict, PyObject *key);
269
270// Get dict lookup for a key, with only hash error, does not create KeyError, 1=ref returned, 0=not
271extern PyObject *DICT_GET_ITEM_WITH_HASH_ERROR1(PyThreadState *tstate, PyObject *dict, PyObject *key);
272extern PyObject *DICT_GET_ITEM_WITH_HASH_ERROR0(PyThreadState *tstate, PyObject *dict, PyObject *key);
273
274// Get dict lookup for a key, similar to PyDict_GetItem, 1=ref returned, 0=not
275extern PyObject *DICT_GET_ITEM1(PyThreadState *tstate, PyObject *dict, PyObject *key);
276extern PyObject *DICT_GET_ITEM0(PyThreadState *tstate, PyObject *dict, PyObject *key);
277
278// Get dict lookup for a key, similar to PyDict_Contains
279extern int DICT_HAS_ITEM(PyThreadState *tstate, PyObject *dict, PyObject *key);
280
281// Convert to dictionary, helper for built-in "dict" mainly.
282extern PyObject *TO_DICT(PyThreadState *tstate, PyObject *seq_obj, PyObject *dict_obj);
283
284// For 3.6 to 3.10, the dictionary version tag is used for caching purposes, so
285// we need to maintain it. However it's private value so we have to play a game
286// of having our own number space inside of their 64 bit numbers.
287#if PYTHON_VERSION >= 0x360 && PYTHON_VERSION <= 0x3c0
288#define _NUITKA_MAINTAIN_DICT_VERSION_TAG 1
289#endif
290
291#if _NUITKA_MAINTAIN_DICT_VERSION_TAG
292extern uint64_t nuitka_dict_version_tag_counter;
293#endif
294
295NUITKA_MAY_BE_UNUSED static void UPDATE_STRING_DICT0(PyDictObject *dict, Nuitka_StringObject *key, PyObject *value) {
296 CHECK_OBJECT(value);
297
298 Nuitka_DictEntryHandle entry = GET_STRING_DICT_ENTRY(dict, key);
299
300#if PYTHON_VERSION >= 0x360
301 if (entry == NULL) {
302 DICT_SET_ITEM((PyObject *)dict, (PyObject *)key, value);
303 return;
304 }
305#endif
306
307 PyObject *old = GET_DICT_ENTRY_VALUE(entry);
308 CHECK_OBJECT_X(old);
309
310 if (value != old) {
311
312 // Values are more likely (more often) set than not set, in that case
313 // speculatively try the quickest access method.
314 if (likely(old != NULL)) {
315 Py_INCREF(value);
316 SET_DICT_ENTRY_VALUE(entry, value);
317
318#if _NUITKA_MAINTAIN_DICT_VERSION_TAG
319 dict->ma_version_tag = nuitka_dict_version_tag_counter++;
320#endif
321 CHECK_OBJECT(old);
322
323 Py_DECREF(old);
324 } else {
325 DICT_SET_ITEM((PyObject *)dict, (PyObject *)key, value);
326 }
327 }
328}
329
330NUITKA_MAY_BE_UNUSED static void UPDATE_STRING_DICT_INPLACE(PyDictObject *dict, Nuitka_StringObject *key,
331 PyObject *value) {
332 CHECK_OBJECT(value);
333
334 Nuitka_DictEntryHandle entry = GET_STRING_DICT_ENTRY(dict, key);
335
336#if PYTHON_VERSION >= 0x360
337 if (entry == NULL) {
338 DICT_SET_ITEM((PyObject *)dict, (PyObject *)key, value);
339
340 Py_DECREF(value);
341 CHECK_OBJECT(value);
342
343 return;
344 }
345#endif
346
347 PyObject *old = GET_DICT_ENTRY_VALUE(entry);
348
349 if (old != value) {
350 // Values are more likely (more often) set than not set, in that case
351 // speculatively try the quickest access method.
352 if (likely(old != NULL)) {
353 SET_DICT_ENTRY_VALUE(entry, value);
354
355#if _NUITKA_MAINTAIN_DICT_VERSION_TAG
356 dict->ma_version_tag = nuitka_dict_version_tag_counter++;
357#endif
358 } else {
359 DICT_SET_ITEM((PyObject *)dict, (PyObject *)key, value);
360 Py_DECREF(value);
361
362 CHECK_OBJECT(value);
363 }
364 } else {
365 Py_DECREF(value);
366 CHECK_OBJECT(value);
367 }
368}
369
370NUITKA_MAY_BE_UNUSED static void UPDATE_STRING_DICT1(PyDictObject *dict, Nuitka_StringObject *key, PyObject *value) {
371 CHECK_OBJECT(key);
372 CHECK_OBJECT(value);
373
374 Nuitka_DictEntryHandle entry = GET_STRING_DICT_ENTRY(dict, key);
375
376#if PYTHON_VERSION >= 0x360
377 if (entry == NULL) {
378 DICT_SET_ITEM((PyObject *)dict, (PyObject *)key, value);
379
380 Py_DECREF(value);
381 CHECK_OBJECT(value);
382
383 return;
384 }
385#endif
386
387 PyObject *old = GET_DICT_ENTRY_VALUE(entry);
388 CHECK_OBJECT_X(old);
389
390 if (old != value) {
391 // Values are more likely (more often) set than not set, in that case
392 // speculatively try the quickest access method.
393 if (likely(old != NULL)) {
394 SET_DICT_ENTRY_VALUE(entry, value);
395
396#if _NUITKA_MAINTAIN_DICT_VERSION_TAG
397 dict->ma_version_tag = nuitka_dict_version_tag_counter++;
398#endif
399 Py_DECREF(old);
400 } else {
401 DICT_SET_ITEM((PyObject *)dict, (PyObject *)key, value);
402
403 Py_DECREF(value);
404 CHECK_OBJECT(value);
405 }
406 } else {
407 Py_DECREF(value);
408 CHECK_OBJECT(value);
409 }
410}
411
412#if PYTHON_VERSION < 0x300
413// Python2 dictionary keys, return a list of keys
414extern PyObject *DICT_KEYS(PyObject *dict);
415// Python2 dictionary items, return a list of values
416extern PyObject *DICT_VALUES(PyObject *dict);
417// Python2 dictionary items, return a list of key/value tuples
418extern PyObject *DICT_ITEMS(PyObject *dict);
419#endif
420
421// Python3 dictionary keys, Python2 iterkeys returns dictionary keys iterator
422extern PyObject *DICT_ITERKEYS(PyThreadState *tstate, PyObject *dict);
423
424// Python3 dictionary values, Python2 itervalues returns dictionary values iterator
425extern PyObject *DICT_ITERVALUES(PyThreadState *tstate, PyObject *dict);
426
427// Python3 dictionary items, Python2 iteritems returns dictionary items iterator
428extern PyObject *DICT_ITERITEMS(PyThreadState *tstate, PyObject *dict);
429
430// Python dictionary keys view
431extern PyObject *DICT_VIEWKEYS(PyObject *dict);
432
433// Python dictionary values view
434extern PyObject *DICT_VIEWVALUES(PyObject *dict);
435
436// Python dictionary items view
437extern PyObject *DICT_VIEWITEMS(PyObject *dict);
438
439// Python dictionary copy, return a shallow copy of a dictionary.
440extern PyObject *DICT_COPY(PyThreadState *tstate, PyObject *dict);
441
442// Python dictionary clear, empties the dictionary.
443extern void DICT_CLEAR(PyObject *dict);
444
445// Replacement for PyDict_Next that is faster (to call).
446extern bool Nuitka_DictNext(PyObject *dict, Py_ssize_t *pos, PyObject **key_ptr, PyObject **value_ptr);
447
448#if PYTHON_VERSION >= 0x3a0 && !defined(_NUITKA_EXPERIMENTAL_DISABLE_FREELIST_ALL) && \
449 !defined(_NUITKA_EXPERIMENTAL_DISABLE_FREELIST_DICT)
450#define NUITKA_DICT_HAS_FREELIST 1
451
452// Replacement for PyDict_New that is faster
453extern PyObject *MAKE_DICT_EMPTY(PyThreadState *tstate);
454#else
455#define NUITKA_DICT_HAS_FREELIST 0
456#define MAKE_DICT_EMPTY(tstate) PyDict_New()
457#endif
458
459// Create a dictionary from key/value pairs.
460extern PyObject *MAKE_DICT(PyObject **pairs, Py_ssize_t size);
461// Create a dictionary from key/value pairs (NULL value means skip)
462extern PyObject *MAKE_DICT_X(PyObject **pairs, Py_ssize_t size);
463// Create a dictionary from key/value pairs (NULL value means skip) where keys are C strings.
464extern PyObject *MAKE_DICT_X_CSTR(char const **keys, PyObject **values, Py_ssize_t size);
465
466#endif
467
468// Part of "Nuitka", an optimizing Python compiler that is compatible and
469// integrates with CPython, but also works on its own.
470//
471// Licensed under the Apache License, Version 2.0 (the "License");
472// you may not use this file except in compliance with the License.
473// You may obtain a copy of the License at
474//
475// http://www.apache.org/licenses/LICENSE-2.0
476//
477// Unless required by applicable law or agreed to in writing, software
478// distributed under the License is distributed on an "AS IS" BASIS,
479// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
480// See the License for the specific language governing permissions and
481// limitations under the License.
Definition helpers.h:10