Ruby 4.1.0dev (2026-09-07 revision b57404b461ba8bf34e802d86b0db78388216e182)
id_table.c (b57404b461ba8bf34e802d86b0db78388216e182)
1/* This file is included by symbol.c */
2
3#include "id_table.h"
4
5#ifndef ID_TABLE_DEBUG
6#define ID_TABLE_DEBUG 0
7#endif
8
9#if ID_TABLE_DEBUG == 0
10#undef NDEBUG
11#define NDEBUG
12#endif
13#include "ruby_assert.h"
14
15typedef rb_id_serial_t id_key_t;
16
17static inline ID
18key2id(id_key_t key)
19{
20 return rb_id_serial_to_id(key);
21}
22
23static inline id_key_t
24id2key(ID id)
25{
26 return rb_id_to_serial(id);
27}
28
29/* simple open addressing with quadratic probing.
30 uses mark-bit on collisions - need extra 1 bit,
31 ID is strictly 3 bits larger than rb_id_serial_t */
32
33#if SIZEOF_VALUE == 8
34/* The table body is a single buffer laid out as:
35
36 [VALUE values[capa] | id_key_t keys[capa] | collision bitmap]
37
38 where the collision bitmap uses one mark bit per slot. Keeping the
39 keys out of the item struct avoids padding them to the alignment of
40 VALUE. The three regions are computed from buf and capa, so the
41 struct only needs to store the buffer pointer. */
42#define COLLISION_TABLE_SIZE(capa) roomof((size_t)(capa), CHAR_BIT)
43#define ID_TABLE_BUF_SIZE(capa) \
44 ((sizeof(VALUE) + sizeof(id_key_t)) * (size_t)(capa) + COLLISION_TABLE_SIZE(capa))
45
46static inline VALUE *
47id_table_items(struct rb_id_table *tbl)
48{
49 return (VALUE *)tbl->buf;
50}
51
52static inline id_key_t *
53id_table_keys(struct rb_id_table *tbl)
54{
55 return (id_key_t *)(id_table_items(tbl) + tbl->capa);
56}
57
58static inline uint8_t *
59id_table_collision_table(struct rb_id_table *tbl)
60{
61 return (uint8_t *)(id_table_keys(tbl) + tbl->capa);
62}
63
64#define ITEM_GET_KEY(tbl, i) (id_table_keys(tbl)[i])
65#define ITEM_KEY_ISSET(tbl, i) ((tbl)->buf && id_table_keys(tbl)[i])
66#define ITEM_COLLIDED(tbl, i) (id_table_collision_table(tbl)[(i) / CHAR_BIT] & ((uint8_t)1 << ((i) % CHAR_BIT)))
67#define ITEM_SET_COLLIDED(tbl, i) (id_table_collision_table(tbl)[(i) / CHAR_BIT] |= ((uint8_t)1 << ((i) % CHAR_BIT)))
68#define ITEM_VALUE(tbl, i) (id_table_items(tbl)[i])
69
70static inline void
71ITEM_SET_KEY(struct rb_id_table *tbl, int i, id_key_t key)
72{
73 id_table_keys(tbl)[i] = key;
74}
75#else
76typedef struct rb_id_item {
77 id_key_t key;
78 VALUE val;
79} item_t;
80
81#define ID_TABLE_BUF_SIZE(capa) (sizeof(item_t) * (size_t)(capa))
82#define id_table_items(tbl) ((item_t *)(tbl)->buf)
83
84#define ITEM_GET_KEY(tbl, i) (id_table_items(tbl)[i].key >> 1)
85#define ITEM_KEY_ISSET(tbl, i) (id_table_items(tbl)[i].key > 1)
86#define ITEM_COLLIDED(tbl, i) (id_table_items(tbl)[i].key & 1)
87#define ITEM_SET_COLLIDED(tbl, i) (id_table_items(tbl)[i].key |= 1)
88#define ITEM_VALUE(tbl, i) (id_table_items(tbl)[i].val)
89
90static inline void
91ITEM_SET_KEY(struct rb_id_table *tbl, int i, id_key_t key)
92{
93 id_table_items(tbl)[i].key = (key << 1) | ITEM_COLLIDED(tbl, i);
94}
95#endif
96
97static inline int
98round_capa(int capa)
99{
100 /* minsize is 4 */
101 capa >>= 2;
102 capa |= capa >> 1;
103 capa |= capa >> 2;
104 capa |= capa >> 4;
105 capa |= capa >> 8;
106 capa |= capa >> 16;
107 return (capa + 1) << 2;
108}
109
110static void
111id_table_alloc_buf(struct rb_id_table *tbl, int capa)
112{
113#if SIZEOF_VALUE == 8
114 tbl->buf = ruby_xcalloc(1, ID_TABLE_BUF_SIZE(capa));
115#else
116 tbl->buf = ZALLOC_N(item_t, capa);
117#endif
118}
119
120struct rb_id_table *
121rb_id_table_init(struct rb_id_table *tbl, size_t s_capa)
122{
123 int capa = (int)s_capa;
124 MEMZERO(tbl, struct rb_id_table, 1);
125 if (capa > 0) {
126 capa = round_capa(capa);
127 tbl->capa = (int)capa;
128 id_table_alloc_buf(tbl, capa);
129 }
130 return tbl;
131}
132
133struct rb_id_table *
134rb_id_table_create(size_t capa)
135{
136 struct rb_id_table *tbl = ALLOC(struct rb_id_table);
137 return rb_id_table_init(tbl, capa);
138}
139
140void
141rb_id_table_free_items(struct rb_id_table *tbl)
142{
143 xfree(tbl->buf);
144}
145
146void
147rb_id_table_free(struct rb_id_table *tbl)
148{
149 xfree(tbl->buf);
150 xfree(tbl);
151}
152
153void
154rb_id_table_clear(struct rb_id_table *tbl)
155{
156 tbl->num = 0;
157 tbl->used = 0;
158 if (tbl->buf) {
159 memset(tbl->buf, 0, ID_TABLE_BUF_SIZE(tbl->capa));
160 }
161}
162
163size_t
164rb_id_table_size(const struct rb_id_table *tbl)
165{
166 return (size_t)tbl->num;
167}
168
169size_t
170rb_id_table_memsize(const struct rb_id_table *tbl)
171{
172 return ID_TABLE_BUF_SIZE(tbl->capa) + sizeof(struct rb_id_table);
173}
174
175static int
176hash_table_index(struct rb_id_table* tbl, id_key_t key)
177{
178 if (tbl->capa > 0) {
179 int mask = tbl->capa - 1;
180 int ix = key & mask;
181 int d = 1;
182 while (key != ITEM_GET_KEY(tbl, ix)) {
183 if (!ITEM_COLLIDED(tbl, ix))
184 return -1;
185 ix = (ix + d) & mask;
186 d++;
187 }
188 return ix;
189 }
190 return -1;
191}
192
193static void
194hash_table_raw_insert(struct rb_id_table *tbl, id_key_t key, VALUE val)
195{
196 int mask = tbl->capa - 1;
197 int ix = key & mask;
198 int d = 1;
199 RUBY_ASSERT(key != 0);
200 while (ITEM_KEY_ISSET(tbl, ix)) {
201 ITEM_SET_COLLIDED(tbl, ix);
202 ix = (ix + d) & mask;
203 d++;
204 }
205 tbl->num++;
206 if (!ITEM_COLLIDED(tbl, ix)) {
207 tbl->used++;
208 }
209 ITEM_SET_KEY(tbl, ix, key);
210 ITEM_VALUE(tbl, ix) = val;
211}
212
213static int
214hash_delete_index(struct rb_id_table *tbl, int ix)
215{
216 if (ix >= 0) {
217 if (!ITEM_COLLIDED(tbl, ix)) {
218 tbl->used--;
219 }
220 tbl->num--;
221 ITEM_SET_KEY(tbl, ix, 0);
222 ITEM_VALUE(tbl, ix) = 0;
223 return TRUE;
224 }
225 else {
226 return FALSE;
227 }
228}
229
230static void
231hash_table_extend(struct rb_id_table* tbl)
232{
233 if (tbl->used + (tbl->used >> 1) >= tbl->capa) {
234 int new_cap = round_capa(tbl->num + (tbl->num >> 1));
235 int i;
236 void *old;
237 struct rb_id_table tmp_tbl = {0};
238 if (new_cap < tbl->capa) {
239 new_cap = round_capa(tbl->used + (tbl->used >> 1));
240 }
241 tmp_tbl.capa = new_cap;
242 id_table_alloc_buf(&tmp_tbl, new_cap);
243 for (i = 0; i < tbl->capa; i++) {
244 id_key_t key = ITEM_GET_KEY(tbl, i);
245 if (key != 0) {
246 hash_table_raw_insert(&tmp_tbl, key, ITEM_VALUE(tbl, i));
247 }
248 }
249 old = tbl->buf;
250 *tbl = tmp_tbl;
251 xfree(old);
252 }
253}
254
255#if ID_TABLE_DEBUG && 0
256static void
257hash_table_show(struct rb_id_table *tbl)
258{
259 const int capa = tbl->capa;
260 int i;
261
262 fprintf(stderr, "tbl: %p (capa: %d, num: %d, used: %d)\n", tbl, tbl->capa, tbl->num, tbl->used);
263 for (i=0; i<capa; i++) {
264 if (ITEM_KEY_ISSET(tbl, i)) {
265 const id_key_t key = ITEM_GET_KEY(tbl, i);
266 fprintf(stderr, " -> [%d] %s %d\n", i, rb_id2name(key2id(key)), (int)key);
267 }
268 }
269}
270#endif
271
272int
273rb_id_table_lookup(struct rb_id_table *tbl, ID id, VALUE *valp)
274{
275 id_key_t key = id2key(id);
276 int index = hash_table_index(tbl, key);
277
278 if (index >= 0) {
279 *valp = ITEM_VALUE(tbl, index);
280 return TRUE;
281 }
282 else {
283 return FALSE;
284 }
285}
286
287static int
288rb_id_table_insert_key(struct rb_id_table *tbl, const id_key_t key, const VALUE val)
289{
290 const int index = hash_table_index(tbl, key);
291
292 if (index >= 0) {
293 ITEM_VALUE(tbl, index) = val;
294 }
295 else {
296 hash_table_extend(tbl);
297 hash_table_raw_insert(tbl, key, val);
298 }
299 return TRUE;
300}
301
302int
303rb_id_table_insert(struct rb_id_table *tbl, ID id, VALUE val)
304{
305 return rb_id_table_insert_key(tbl, id2key(id), val);
306}
307
308int
309rb_id_table_delete(struct rb_id_table *tbl, ID id)
310{
311 const id_key_t key = id2key(id);
312 int index = hash_table_index(tbl, key);
313 return hash_delete_index(tbl, index);
314}
315
316void
317rb_id_table_foreach(struct rb_id_table *tbl, rb_id_table_foreach_func_t *func, void *data)
318{
319 int i, capa = tbl->capa;
320
321 for (i=0; i<capa; i++) {
322 if (ITEM_KEY_ISSET(tbl, i)) {
323 const id_key_t key = ITEM_GET_KEY(tbl, i);
324 enum rb_id_table_iterator_result ret = (*func)(key2id(key), ITEM_VALUE(tbl, i), data);
325 RUBY_ASSERT(key != 0);
326
327 if (ret == ID_TABLE_DELETE)
328 hash_delete_index(tbl, i);
329 else if (ret == ID_TABLE_STOP)
330 return;
331 }
332 }
333}
334
335void
336rb_id_table_foreach_values(struct rb_id_table *tbl, rb_id_table_foreach_values_func_t *func, void *data)
337{
338 int i, capa = tbl->capa;
339
340 if (!tbl->buf) {
341 return;
342 }
343
344 for (i=0; i<capa; i++) {
345 if (ITEM_KEY_ISSET(tbl, i)) {
346 enum rb_id_table_iterator_result ret = (*func)(ITEM_VALUE(tbl, i), data);
347
348 if (ret == ID_TABLE_DELETE)
349 hash_delete_index(tbl, i);
350 else if (ret == ID_TABLE_STOP)
351 return;
352 }
353 }
354}
355
356void
357rb_id_table_foreach_values_with_replace(struct rb_id_table *tbl, rb_id_table_foreach_values_func_t *func, rb_id_table_update_value_callback_func_t *replace, void *data)
358{
359 int i, capa = tbl->capa;
360
361 for (i = 0; i < capa; i++) {
362 if (ITEM_KEY_ISSET(tbl, i)) {
363 enum rb_id_table_iterator_result ret = (*func)(ITEM_VALUE(tbl, i), data);
364
365 if (ret == ID_TABLE_REPLACE) {
366 VALUE val = ITEM_VALUE(tbl, i);
367 ret = (*replace)(&val, data, TRUE);
368 ITEM_VALUE(tbl, i) = val;
369 }
370
371 if (ret == ID_TABLE_STOP)
372 return;
373 }
374 }
375}
376
377static void
378managed_id_table_free(void *data)
379{
380 struct rb_id_table *tbl = (struct rb_id_table *)data;
381 rb_id_table_free_items(tbl);
382}
383
384static size_t
385managed_id_table_memsize(const void *data)
386{
387 const struct rb_id_table *tbl = (const struct rb_id_table *)data;
388 return rb_id_table_memsize(tbl) - sizeof(struct rb_id_table);
389}
390
391const rb_data_type_t rb_managed_id_table_type = {
392 .wrap_struct_name = "VM/managed_id_table",
393 .function = {
394 .dmark = NULL, // Nothing to mark
395 .dfree = managed_id_table_free,
396 .dsize = managed_id_table_memsize,
397 },
398 .flags = RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE,
399};
400
401static inline struct rb_id_table *
402managed_id_table_ptr(VALUE obj)
403{
405 RUBY_ASSERT(rb_typeddata_inherited_p(RTYPEDDATA_TYPE(obj), &rb_managed_id_table_type));
406
407 return RTYPEDDATA_GET_DATA(obj);
408}
409
410VALUE
411rb_managed_id_table_create(const rb_data_type_t *type, size_t capa)
412{
413 struct rb_id_table *tbl;
414 VALUE obj = TypedData_Make_Struct(0, struct rb_id_table, type, tbl);
415 RB_OBJ_SET_SHAREABLE(obj);
416 rb_id_table_init(tbl, capa); // NOTE: this can cause GC, so dmark and dsize need to check tbl->buf
417 return obj;
418}
419
420VALUE
421rb_managed_id_table_new(size_t capa)
422{
423 return rb_managed_id_table_create(&rb_managed_id_table_type, capa);
424}
425
426static enum rb_id_table_iterator_result
427managed_id_table_dup_i(ID id, VALUE val, void *data)
428{
429 struct rb_id_table *new_tbl = (struct rb_id_table *)data;
430 rb_id_table_insert(new_tbl, id, val);
431 return ID_TABLE_CONTINUE;
432}
433
434VALUE
435rb_managed_id_table_dup(VALUE old_table)
436{
437 struct rb_id_table *new_tbl;
438 VALUE obj = TypedData_Make_Struct(0, struct rb_id_table, RTYPEDDATA_TYPE(old_table), new_tbl);
439 /* A managed id table hangs off VM-global state (e.g. a shape tree's edge
440 * table grows via this dup) and is reachable from every Ractor, so mark it
441 * shareable. */
442 RB_OBJ_SET_SHAREABLE(obj);
443 struct rb_id_table *old_tbl = managed_id_table_ptr(old_table);
444 rb_id_table_init(new_tbl, old_tbl->num + 1);
445 rb_id_table_foreach(old_tbl, managed_id_table_dup_i, new_tbl);
446 /* The table body is embedded (RUBY_TYPED_EMBEDDABLE), so old_tbl is an
447 * interior pointer. Keep old_table live in case dup_i triggers a GC. */
448 RB_GC_GUARD(old_table);
449 return obj;
450}
451
452int
453rb_managed_id_table_lookup(VALUE table, ID id, VALUE *valp)
454{
455 return rb_id_table_lookup(managed_id_table_ptr(table), id, valp);
456}
457
458int
459rb_managed_id_table_insert(VALUE table, ID id, VALUE val)
460{
461 return rb_id_table_insert(managed_id_table_ptr(table), id, val);
462}
463
464size_t
465rb_managed_id_table_size(VALUE table)
466{
467 return rb_id_table_size(managed_id_table_ptr(table));
468}
469
470void
471rb_managed_id_table_foreach(VALUE table, rb_id_table_foreach_func_t *func, void *data)
472{
473 rb_id_table_foreach(managed_id_table_ptr(table), func, data);
474 /* The table body is embedded (RUBY_TYPED_EMBEDDABLE), so the pointer above
475 * is interior. Keep table live in case func triggers a GC. */
476 RB_GC_GUARD(table);
477}
478
479void
480rb_managed_id_table_foreach_values(VALUE table, rb_id_table_foreach_values_func_t *func, void *data)
481{
482 rb_id_table_foreach_values(managed_id_table_ptr(table), func, data);
483 RB_GC_GUARD(table);
484}
485
486int
487rb_managed_id_table_delete(VALUE table, ID id)
488{
489 return rb_id_table_delete(managed_id_table_ptr(table), id);
490}
491
492static enum rb_id_table_iterator_result
493marked_id_table_mark_i(VALUE val, void *data)
494{
495 rb_gc_mark_movable(val);
496 return ID_TABLE_CONTINUE;
497}
498
499static void
500marked_id_table_mark(void *ptr)
501{
502 struct rb_id_table *tbl = (struct rb_id_table *)ptr;
503 rb_id_table_foreach_values(tbl, marked_id_table_mark_i, NULL);
504}
505
506static enum rb_id_table_iterator_result
507marked_id_table_compact_check_i(VALUE value, void *data)
508{
509 if (rb_gc_location(value) != value) {
510 return ID_TABLE_REPLACE;
511 }
512 return ID_TABLE_CONTINUE;
513}
514
515static enum rb_id_table_iterator_result
516marked_id_table_compact_replace_i(VALUE *value, void *data, int existing)
517{
518 *value = rb_gc_location(*value);
519 return ID_TABLE_CONTINUE;
520}
521
522static void
523marked_id_table_compact(void *ptr)
524{
525 struct rb_id_table *tbl = (struct rb_id_table *)ptr;
526 rb_id_table_foreach_values_with_replace(tbl, marked_id_table_compact_check_i, marked_id_table_compact_replace_i, NULL);
527}
528
529const rb_data_type_t rb_marked_id_table_type = {
530 .wrap_struct_name = "VM/marked_id_table",
531 .function = {
532 .dmark = marked_id_table_mark,
533 .dfree = managed_id_table_free,
534 .dsize = managed_id_table_memsize,
535 .dcompact = marked_id_table_compact,
536 },
537 .parent = &rb_managed_id_table_type,
538 .flags = RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE,
539};
540
541VALUE
542rb_marked_id_table_new(size_t capa)
543{
544 return rb_managed_id_table_create(&rb_marked_id_table_type, capa);
545}
546
547int
548rb_marked_id_table_insert(VALUE table, ID id, VALUE val)
549{
550 int result = rb_managed_id_table_insert(table, id, val);
551 RB_OBJ_WRITTEN(table, Qundef, val);
552 return result;
553}
554
555static enum rb_id_table_iterator_result
556marked_id_table_dup_i(VALUE val, void *data)
557{
558 VALUE new_table = (VALUE)data;
559 RB_OBJ_WRITTEN(new_table, Qundef, val);
560 return ID_TABLE_CONTINUE;
561}
562
563VALUE
564rb_marked_id_table_dup(VALUE old_table)
565{
566 VALUE new_table = rb_managed_id_table_dup(old_table);
567 rb_managed_id_table_foreach_values(new_table, marked_id_table_dup_i, (void *)new_table);
568 return new_table;
569}
#define RUBY_ASSERT(...)
Asserts that the given expression is truthy if and only if RUBY_DEBUG is truthy.
Definition assert.h:219
#define ALLOC
Old name of RB_ALLOC.
Definition memory.h:400
#define xfree
Old name of ruby_xfree.
Definition xmalloc.h:58
#define Qundef
Old name of RUBY_Qundef.
#define T_DATA
Old name of RUBY_T_DATA.
Definition value_type.h:60
#define ZALLOC_N
Old name of RB_ZALLOC_N.
Definition memory.h:401
#define RB_OBJ_WRITTEN(old, oldv, young)
Identical to RB_OBJ_WRITE(), except it doesn't write any values, but only a WB declaration.
Definition gc.h:468
int capa
Designed capacity of the buffer.
Definition io.h:11
#define MEMZERO(p, type, n)
Handy macro to erase a region of memory.
Definition memory.h:360
#define RB_GC_GUARD(v)
Prevents premature destruction of local objects.
Definition memory.h:167
VALUE type(ANYARGS)
ANYARGS-ed function type.
static const rb_data_type_t * RTYPEDDATA_TYPE(VALUE obj)
Queries for the type of given object.
Definition rtypeddata.h:692
#define TypedData_Make_Struct(klass, type, data_type, sval)
Identical to TypedData_Wrap_Struct, except it allocates a new data region internally instead of takin...
Definition rtypeddata.h:604
This is the struct that holds necessary info for a struct.
Definition rtypeddata.h:238
const char * wrap_struct_name
Name of structs of this kind.
Definition rtypeddata.h:245
VALUE flags
Type-specific behavioural characteristics.
Definition rtypeddata.h:352
uintptr_t ID
Type that represents a Ruby identifier such as a variable name.
Definition value.h:52
uintptr_t VALUE
Type that represents a Ruby object.
Definition value.h:40
static bool RB_TYPE_P(VALUE obj, enum ruby_value_type t)
Queries if the given object is of given type.
Definition value_type.h:376