40
40
#define DEBUG_printf (...) (void)0
41
41
#endif
42
42
43
- #if MICROPY_OPT_MAP_LOOKUP_CACHE
44
- // MP_STATE_VM(map_lookup_cache) provides a cache of index to the last known
45
- // position of that index in any map. On a cache hit, this allows
46
- // short-circuiting the full linear search in the case of an ordered map
47
- // (i.e. all builtin modules and objects' locals dicts), and computation of
48
- // the hash (and potentially some linear probing) in the case of a regular
49
- // map. Note the same cache is shared across all maps.
50
-
51
- // Gets the index into the cache for this index. Shift down by two to remove
52
- // mp_obj_t tag bits.
53
- #define MAP_CACHE_OFFSET (index ) ((((uintptr_t)(index)) >> 2) % MICROPY_OPT_MAP_LOOKUP_CACHE_SIZE)
54
- // Gets the map cache entry for the corresponding index.
55
- #define MAP_CACHE_ENTRY (index ) (MP_STATE_VM(map_lookup_cache)[MAP_CACHE_OFFSET(index)])
56
- // Retrieve the mp_obj_t at the location suggested by the cache.
57
- #define MAP_CACHE_GET (map , index ) (&(map)->table[MAP_CACHE_ENTRY(index) % (map)->alloc])
58
- // Update the cache for this index.
59
- #define MAP_CACHE_SET (index , pos ) MAP_CACHE_ENTRY(index) = (pos) & 0xff;
60
- #else
61
- #define MAP_CACHE_SET (index , pos )
62
- #endif
63
-
64
43
// This table of sizes is used to control the growth of hash tables.
65
44
// The first set of sizes are chosen so the allocation fits exactly in a
66
45
// 4-word GC block, and it's not so important for these small values to be
@@ -128,7 +107,7 @@ void mp_map_clear(mp_map_t *map) {
128
107
map -> table = NULL ;
129
108
}
130
109
131
- STATIC void mp_map_rehash (mp_map_t * map ) {
110
+ void mp_map_rehash (mp_map_t * map ) {
132
111
size_t old_alloc = map -> alloc ;
133
112
size_t new_alloc = get_hash_alloc_greater_or_equal_to (map -> alloc + 1 );
134
113
DEBUG_printf ("mp_map_rehash(%p): " UINT_FMT " -> " UINT_FMT "\n" , map , old_alloc , new_alloc );
@@ -147,209 +126,6 @@ STATIC void mp_map_rehash(mp_map_t *map) {
147
126
m_del (mp_map_elem_t , old_table , old_alloc );
148
127
}
149
128
150
- // MP_MAP_LOOKUP behaviour:
151
- // - returns NULL if not found, else the slot it was found in with key,value non-null
152
- // MP_MAP_LOOKUP_ADD_IF_NOT_FOUND behaviour:
153
- // - returns slot, with key non-null and value=MP_OBJ_NULL if it was added
154
- // MP_MAP_LOOKUP_REMOVE_IF_FOUND behaviour:
155
- // - returns NULL if not found, else the slot if was found in with key null and value non-null
156
- mp_map_elem_t * MICROPY_WRAP_MP_MAP_LOOKUP (mp_map_lookup )(mp_map_t * map , mp_obj_t index , mp_map_lookup_kind_t lookup_kind ) {
157
- // If the map is a fixed array then we must only be called for a lookup
158
- assert (!map -> is_fixed || lookup_kind == MP_MAP_LOOKUP );
159
-
160
- #if MICROPY_OPT_MAP_LOOKUP_CACHE
161
- // Try the cache for lookup or add-if-not-found.
162
- if (lookup_kind != MP_MAP_LOOKUP_REMOVE_IF_FOUND && map -> alloc ) {
163
- mp_map_elem_t * slot = MAP_CACHE_GET (map , index );
164
- // Note: Just comparing key for value equality will have false negatives, but
165
- // these will be handled by the regular path below.
166
- if (slot -> key == index ) {
167
- return slot ;
168
- }
169
- }
170
- #endif
171
-
172
- // Work out if we can compare just pointers
173
- bool compare_only_ptrs = map -> all_keys_are_qstrs ;
174
- if (compare_only_ptrs ) {
175
- if (mp_obj_is_qstr (index )) {
176
- // Index is a qstr, so can just do ptr comparison.
177
- } else if (mp_obj_is_exact_type (index , & mp_type_str )) {
178
- // Index is a non-interned string.
179
- // We can either intern the string, or force a full equality comparison.
180
- // We chose the latter, since interning costs time and potentially RAM,
181
- // and it won't necessarily benefit subsequent calls because these calls
182
- // most likely won't pass the newly-interned string.
183
- compare_only_ptrs = false;
184
- } else if (lookup_kind != MP_MAP_LOOKUP_ADD_IF_NOT_FOUND ) {
185
- // If we are not adding, then we can return straight away a failed
186
- // lookup because we know that the index will never be found.
187
- return NULL ;
188
- }
189
- }
190
-
191
- // if the map is an ordered array then we must do a brute force linear search
192
- if (map -> is_ordered ) {
193
- for (mp_map_elem_t * elem = & map -> table [0 ], * top = & map -> table [map -> used ]; elem < top ; elem ++ ) {
194
- if (elem -> key == index || (!compare_only_ptrs && mp_obj_equal (elem -> key , index ))) {
195
- #if MICROPY_PY_COLLECTIONS_ORDEREDDICT
196
- if (MP_UNLIKELY (lookup_kind == MP_MAP_LOOKUP_REMOVE_IF_FOUND )) {
197
- // remove the found element by moving the rest of the array down
198
- mp_obj_t value = elem -> value ;
199
- -- map -> used ;
200
- memmove (elem , elem + 1 , (top - elem - 1 ) * sizeof (* elem ));
201
- // put the found element after the end so the caller can access it if needed
202
- // note: caller must NULL the value so the GC can clean up (e.g. see dict_get_helper).
203
- elem = & map -> table [map -> used ];
204
- elem -> key = MP_OBJ_NULL ;
205
- elem -> value = value ;
206
- }
207
- #endif
208
- MAP_CACHE_SET (index , elem - map -> table );
209
- return elem ;
210
- }
211
- }
212
- #if MICROPY_PY_COLLECTIONS_ORDEREDDICT
213
- if (MP_LIKELY (lookup_kind != MP_MAP_LOOKUP_ADD_IF_NOT_FOUND )) {
214
- return NULL ;
215
- }
216
- if (map -> used == map -> alloc ) {
217
- // TODO: Alloc policy
218
- map -> alloc += 4 ;
219
- map -> table = m_renew (mp_map_elem_t , map -> table , map -> used , map -> alloc );
220
- mp_seq_clear (map -> table , map -> used , map -> alloc , sizeof (* map -> table ));
221
- }
222
- mp_map_elem_t * elem = map -> table + map -> used ++ ;
223
- elem -> key = index ;
224
- elem -> value = MP_OBJ_NULL ;
225
- if (!mp_obj_is_qstr (index )) {
226
- map -> all_keys_are_qstrs = 0 ;
227
- }
228
- return elem ;
229
- #else
230
- return NULL ;
231
- #endif
232
- }
233
-
234
- // map is a hash table (not an ordered array), so do a hash lookup
235
-
236
- if (map -> alloc == 0 ) {
237
- if (lookup_kind == MP_MAP_LOOKUP_ADD_IF_NOT_FOUND ) {
238
- mp_map_rehash (map );
239
- } else {
240
- return NULL ;
241
- }
242
- }
243
-
244
- // When MICROPY_QSTR_BYTES_IN_HASH is zero, for qstr keys we use the qstr
245
- // value (i.e. the index in the qstr pool) as the hash value as it is
246
- // free to compute and also a very effective hash as it is unique across
247
- // all qstrs.
248
- //
249
- // However, non-qstr strings (i.e. str) still use their "true" hash
250
- // (i.e. computed using qstr_hash) which will be different. So this means
251
- // that identical strings will hash differently depending on whether they
252
- // are qstr or str. It should be rare for a program to be working with
253
- // both but it is possible.
254
- //
255
- // So this means that in certain situations (see below), we cannot rely on
256
- // the linear probing finding an unused slot as the signal that the key
257
- // does not exist, as it's possible the key was inserted using
258
- // its "other" hash, and must fall back to searching the entire table. We
259
- // can still use the hash as a good hint for the starting location
260
- // though. This flag must be set to false in these situations.
261
- bool stop_at_empty = true;
262
-
263
- // get hash of index, with fast path for common case of qstr
264
- mp_uint_t hash ;
265
- if (mp_obj_is_qstr (index )) {
266
- #if MICROPY_QSTR_BYTES_IN_HASH
267
- hash = qstr_hash (MP_OBJ_QSTR_VALUE (index ));
268
- #else
269
- // Optimisation -- use the qstr index directly as the hash.
270
- hash = MP_OBJ_QSTR_VALUE (index );
271
- // If there are non-qstr keys in this map, we must assume that there
272
- // may possibly be str keys.
273
- stop_at_empty = map -> all_keys_are_qstrs ;
274
- #endif
275
- } else {
276
- hash = MP_OBJ_SMALL_INT_VALUE (mp_unary_op (MP_UNARY_OP_HASH , index ));
277
-
278
- #if MICROPY_QSTR_BYTES_IN_HASH == 0
279
- if (mp_obj_is_exact_type (index , & mp_type_str )) {
280
- // We must assume there might be qstr keys.
281
- stop_at_empty = false;
282
- }
283
- #endif
284
- }
285
-
286
- size_t pos = hash % map -> alloc ;
287
- size_t start_pos = pos ;
288
- mp_map_elem_t * avail_slot = NULL ;
289
- for (;;) {
290
- mp_map_elem_t * slot = & map -> table [pos ];
291
- if (slot -> key == MP_OBJ_NULL ) {
292
- // found an empty slot, remember for later
293
- if (avail_slot == NULL ) {
294
- avail_slot = slot ;
295
- }
296
- if (stop_at_empty ) {
297
- // safe to assume that the key doesn't exist, so pretend like
298
- // we searched the entire table
299
- goto search_over ;
300
- }
301
- } else if (slot -> key == MP_OBJ_SENTINEL ) {
302
- // found deleted slot, remember for later
303
- if (avail_slot == NULL ) {
304
- avail_slot = slot ;
305
- }
306
- } else if (slot -> key == index || (!compare_only_ptrs && mp_obj_equal (slot -> key , index ))) {
307
- // found index
308
- // Note: CPython does not replace the index; try x={True:'true'};x[1]='one';x
309
- if (lookup_kind == MP_MAP_LOOKUP_REMOVE_IF_FOUND ) {
310
- // delete element in this slot
311
- map -> used -- ;
312
- if (map -> table [(pos + 1 ) % map -> alloc ].key == MP_OBJ_NULL ) {
313
- // optimisation if next slot is empty
314
- slot -> key = MP_OBJ_NULL ;
315
- } else {
316
- slot -> key = MP_OBJ_SENTINEL ;
317
- }
318
- // keep slot->value so that caller can access it if needed
319
- }
320
- MAP_CACHE_SET (index , pos );
321
- return slot ;
322
- }
323
-
324
- // not yet found, keep searching in this table
325
- pos = (pos + 1 ) % map -> alloc ;
326
-
327
- if (pos == start_pos ) {
328
- search_over :
329
- // search got back to starting position (or found empty slot), so index is not in table
330
- if (lookup_kind == MP_MAP_LOOKUP_ADD_IF_NOT_FOUND ) {
331
- if (avail_slot != NULL ) {
332
- // there was an available slot, so use that
333
- map -> used ++ ;
334
- avail_slot -> key = index ;
335
- avail_slot -> value = MP_OBJ_NULL ;
336
- if (!mp_obj_is_qstr (index )) {
337
- map -> all_keys_are_qstrs = 0 ;
338
- }
339
- return avail_slot ;
340
- } else {
341
- // not enough room in table, rehash it
342
- mp_map_rehash (map );
343
- // restart the search for the new element
344
- start_pos = pos = hash % map -> alloc ;
345
- }
346
- } else {
347
- return NULL ;
348
- }
349
- }
350
- }
351
- }
352
-
353
129
/******************************************************************************/
354
130
/* set */
355
131
0 commit comments