Ruby 4.1.0dev (2025-12-30 revision d5af8d785888f3af5efb844be5948df71d777b22)
weakmap.c (d5af8d785888f3af5efb844be5948df71d777b22)
1#include "internal.h"
2#include "internal/gc.h"
3#include "internal/hash.h"
4#include "internal/proc.h"
5#include "internal/sanitizers.h"
6#include "ruby/st.h"
7
8/* ===== WeakMap =====
9 *
10 * WeakMap contains one ST table which contains a pointer to the object as the
11 * key and a pointer to the object as the value. This means that the key and
12 * value of the table are both of the type `VALUE *`.
13 *
14 * The objects are not directly stored as keys and values in the table because
15 * `rb_gc_mark_weak` requires a pointer to the memory location to overwrite
16 * when the object is reclaimed. Using a pointer into the ST table entry is not
17 * safe because the pointer can change when the ST table is resized.
18 *
19 * WeakMap hashes and compares using the pointer address of the object.
20 *
21 * For performance and memory efficiency reasons, the key and value
22 * are allocated at the same time and adjacent to each other.
23 *
24 * During GC and while iterating, reclaimed entries (i.e. either the key or
25 * value points to `Qundef`) are removed from the ST table.
26 */
27
28struct weakmap {
29 st_table *table;
30};
31
33 VALUE key;
34 VALUE val;
35};
36
37static void
38wmap_free(void *ptr)
39{
40 struct weakmap *w = ptr;
41
42 st_free_table(w->table);
43}
44
45static size_t
46wmap_memsize(const void *ptr)
47{
48 const struct weakmap *w = ptr;
49
50 size_t size = 0;
51 if (w->table) {
52 size += st_memsize(w->table);
53 /* The key and value of the table each take sizeof(VALUE) in size. */
54 size += st_table_size(w->table) * (2 * sizeof(VALUE));
55 }
56
57 return size;
58}
59
61 st_table *table;
62 struct weakmap_entry *dead_entry;
63};
64
65static int
66wmap_compact_table_each_i(st_data_t k, st_data_t v, st_data_t d, int error)
67{
68 st_table *table = (st_table *)d;
69
70 VALUE key = (VALUE)k;
71 VALUE val = (VALUE)v;
72
73 VALUE moved_key = rb_gc_location(key);
74 VALUE moved_val = rb_gc_location(val);
75
76 /* If the key object moves, then we must reinsert because the hash is
77 * based on the pointer rather than the object itself. */
78 if (key != moved_key) {
79 st_insert(table, (st_data_t)moved_key, (st_data_t)moved_val);
80
81 return ST_DELETE;
82 }
83 else if (val != moved_val) {
84 return ST_REPLACE;
85 }
86 else {
87 return ST_CONTINUE;
88 }
89}
90
91static int
92wmap_compact_table_replace_i(st_data_t *k, st_data_t *v, st_data_t d, int existing)
93{
94 RUBY_ASSERT((VALUE)*k == rb_gc_location((VALUE)*k));
95
96 *v = (st_data_t)rb_gc_location((VALUE)*v);
97
98 return ST_CONTINUE;
99}
100
101static void
102wmap_compact(void *ptr)
103{
104 struct weakmap *w = ptr;
105
106 if (w->table) {
107 DURING_GC_COULD_MALLOC_REGION_START();
108 {
109 st_foreach_with_replace(w->table, wmap_compact_table_each_i, wmap_compact_table_replace_i, (st_data_t)w->table);
110 }
111 DURING_GC_COULD_MALLOC_REGION_END();
112 }
113}
114
115const rb_data_type_t rb_weakmap_type = {
116 "weakmap",
117 {
118 NULL,
119 wmap_free,
120 wmap_memsize,
121 wmap_compact,
122 },
123 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE
124};
125
126static int
127wmap_cmp(st_data_t x, st_data_t y)
128{
129 return x != y;
130}
131
132static st_index_t
133wmap_hash(st_data_t n)
134{
135 return st_numhash(n);
136}
137
138static const struct st_hash_type wmap_hash_type = {
139 wmap_cmp,
140 wmap_hash,
141};
142
143static int
144rb_wmap_handle_weak_references_i(st_data_t key, st_data_t val, st_data_t arg)
145{
146 if (rb_gc_handle_weak_references_alive_p(key) &&
147 rb_gc_handle_weak_references_alive_p(val)) {
148 return ST_CONTINUE;
149 }
150 else {
151 return ST_DELETE;
152 }
153}
154
155void
156rb_wmap_handle_weak_references(VALUE self)
157{
158 struct weakmap *w;
159 TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w);
160
161 st_foreach(w->table, rb_wmap_handle_weak_references_i, (st_data_t)0);
162}
163
164static VALUE
165wmap_allocate(VALUE klass)
166{
167 struct weakmap *w;
168 VALUE obj = TypedData_Make_Struct(klass, struct weakmap, &rb_weakmap_type, w);
169
170 w->table = st_init_table(&wmap_hash_type);
171
172 rb_gc_declare_weak_references(obj);
173
174 return obj;
175}
176
177static VALUE
178wmap_inspect_append(VALUE str, VALUE obj)
179{
180 if (SPECIAL_CONST_P(obj)) {
181 return rb_str_append(str, rb_inspect(obj));
182 }
183 else {
184 return rb_str_append(str, rb_any_to_s(obj));
185 }
186}
187
188static int
189wmap_inspect_i(st_data_t k, st_data_t v, st_data_t data)
190{
191 VALUE key = (VALUE)k;
192 VALUE val = (VALUE)v;
193 VALUE str = (VALUE)data;
194
195 if (RSTRING_PTR(str)[0] == '#') {
196 rb_str_cat2(str, ", ");
197 }
198 else {
199 rb_str_cat2(str, ": ");
200 RSTRING_PTR(str)[0] = '#';
201 }
202
203 wmap_inspect_append(str, key);
204 rb_str_cat2(str, " => ");
205 wmap_inspect_append(str, val);
206
207 return ST_CONTINUE;
208}
209
210static VALUE
211wmap_inspect(VALUE self)
212{
213 VALUE c = rb_class_name(CLASS_OF(self));
214 struct weakmap *w;
215 TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w);
216
217 VALUE str = rb_sprintf("-<%"PRIsVALUE":%p", c, (void *)self);
218
219 st_foreach(w->table, wmap_inspect_i, (st_data_t)str);
220
221 RSTRING_PTR(str)[0] = '#';
222 rb_str_cat2(str, ">");
223
224 return str;
225}
226
227static int
228wmap_each_i(st_data_t k, st_data_t v, st_data_t _)
229{
230 rb_yield_values(2, (VALUE)k, (VALUE)v);
231
232 return ST_CONTINUE;
233}
234
235/*
236 * call-seq:
237 * map.each {|key, val| ... } -> self
238 *
239 * Iterates over keys and values. Note that unlike other collections,
240 * +each+ without block isn't supported.
241 *
242 */
243static VALUE
244wmap_each(VALUE self)
245{
246 struct weakmap *w;
247 TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w);
248
249 st_foreach(w->table, wmap_each_i, (st_data_t)0);
250
251 return self;
252}
253
254static int
255wmap_each_key_i(st_data_t k, st_data_t _v, st_data_t _data)
256{
257 rb_yield((VALUE)k);
258
259 return ST_CONTINUE;
260}
261
262/*
263 * call-seq:
264 * map.each_key {|key| ... } -> self
265 *
266 * Iterates over keys. Note that unlike other collections,
267 * +each_key+ without block isn't supported.
268 *
269 */
270static VALUE
271wmap_each_key(VALUE self)
272{
273 struct weakmap *w;
274 TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w);
275
276 st_foreach(w->table, wmap_each_key_i, (st_data_t)0);
277
278 return self;
279}
280
281static int
282wmap_each_value_i(st_data_t k, st_data_t v, st_data_t _data)
283{
284 rb_yield((VALUE)v);
285
286 return ST_CONTINUE;
287}
288
289/*
290 * call-seq:
291 * map.each_value {|val| ... } -> self
292 *
293 * Iterates over values. Note that unlike other collections,
294 * +each_value+ without block isn't supported.
295 *
296 */
297static VALUE
298wmap_each_value(VALUE self)
299{
300 struct weakmap *w;
301 TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w);
302
303 st_foreach(w->table, wmap_each_value_i, (st_data_t)0);
304
305 return self;
306}
307
308static int
309wmap_keys_i(st_data_t k, st_data_t v, st_data_t data)
310{
311 VALUE ary = (VALUE)data;
312
313 rb_ary_push(ary, (VALUE)k);
314
315 return ST_CONTINUE;
316}
317
318/*
319 * call-seq:
320 * map.keys -> new_array
321 *
322 * Returns a new Array containing all keys in the map.
323 *
324 */
325static VALUE
326wmap_keys(VALUE self)
327{
328 struct weakmap *w;
329 TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w);
330
331 VALUE ary = rb_ary_new();
332 st_foreach(w->table, wmap_keys_i, (st_data_t)ary);
333
334 return ary;
335}
336
337static int
338wmap_values_i(st_data_t k, st_data_t v, st_data_t data)
339{
340 VALUE ary = (VALUE)data;
341
342 rb_ary_push(ary, (VALUE)v);
343
344 return ST_CONTINUE;
345}
346
347/*
348 * call-seq:
349 * map.values -> new_array
350 *
351 * Returns a new Array containing all values in the map.
352 *
353 */
354static VALUE
355wmap_values(VALUE self)
356{
357 struct weakmap *w;
358 TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w);
359
360 VALUE ary = rb_ary_new();
361 st_foreach(w->table, wmap_values_i, (st_data_t)ary);
362
363 return ary;
364}
365
366/*
367 * call-seq:
368 * map[key] = value -> value
369 *
370 * Associates the given +value+ with the given +key+.
371 *
372 * If the given +key+ exists, replaces its value with the given +value+;
373 * the ordering is not affected.
374 */
375static VALUE
376wmap_aset(VALUE self, VALUE key, VALUE val)
377{
378 struct weakmap *w;
379 TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w);
380
381 st_insert(w->table, (st_data_t)key, (st_data_t)val);
382
383 RB_OBJ_WRITTEN(self, Qundef, key);
384 RB_OBJ_WRITTEN(self, Qundef, val);
385
386 return Qnil;
387}
388
389/* Retrieves a weakly referenced object with the given key */
390static VALUE
391wmap_lookup(VALUE self, VALUE key)
392{
393 struct weakmap *w;
394 TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w);
395
396 st_data_t data;
397 if (!st_lookup(w->table, (st_data_t)key, &data)) return Qundef;
398
399 return (VALUE)data;
400}
401
402/*
403 * call-seq:
404 * map[key] -> value
405 *
406 * Returns the value associated with the given +key+ if found.
407 *
408 * If +key+ is not found, returns +nil+.
409 */
410static VALUE
411wmap_aref(VALUE self, VALUE key)
412{
413 VALUE obj = wmap_lookup(self, key);
414 return !UNDEF_P(obj) ? obj : Qnil;
415}
416
417/*
418 * call-seq:
419 * map.delete(key) -> value or nil
420 * map.delete(key) {|key| ... } -> object
421 *
422 * Deletes the entry for the given +key+ and returns its associated value.
423 *
424 * If no block is given and +key+ is found, deletes the entry and returns the associated value:
425 * m = ObjectSpace::WeakMap.new
426 * key = "foo"
427 * m[key] = 1
428 * m.delete(key) # => 1
429 * m[key] # => nil
430 *
431 * If no block is given and +key+ is not found, returns +nil+.
432 *
433 * If a block is given and +key+ is found, ignores the block,
434 * deletes the entry, and returns the associated value:
435 * m = ObjectSpace::WeakMap.new
436 * key = "foo"
437 * m[key] = 2
438 * m.delete(key) { |key| raise 'Will never happen'} # => 2
439 *
440 * If a block is given and +key+ is not found,
441 * yields the +key+ to the block and returns the block's return value:
442 * m = ObjectSpace::WeakMap.new
443 * m.delete("nosuch") { |key| "Key #{key} not found" } # => "Key nosuch not found"
444 */
445static VALUE
446wmap_delete(VALUE self, VALUE key)
447{
448 struct weakmap *w;
449 TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w);
450
451 st_data_t orig_key = (st_data_t)key;
452 st_data_t orig_val;
453 if (st_delete(w->table, &orig_key, &orig_val)) {
454 return (VALUE)orig_val;
455 }
456
457 if (rb_block_given_p()) {
458 return rb_yield(key);
459 }
460 else {
461 return Qnil;
462 }
463}
464
465/*
466 * call-seq:
467 * map.key?(key) -> true or false
468 *
469 * Returns +true+ if +key+ is a key in +self+, otherwise +false+.
470 */
471static VALUE
472wmap_has_key(VALUE self, VALUE key)
473{
474 return RBOOL(!UNDEF_P(wmap_lookup(self, key)));
475}
476
477/*
478 * call-seq:
479 * map.size -> number
480 *
481 * Returns the number of referenced objects
482 */
483static VALUE
484wmap_size(VALUE self)
485{
486 struct weakmap *w;
487 TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w);
488
489 st_index_t n = st_table_size(w->table);
490
491#if SIZEOF_ST_INDEX_T <= SIZEOF_LONG
492 return ULONG2NUM(n);
493#else
494 return ULL2NUM(n);
495#endif
496}
497
498/* ===== WeakKeyMap =====
499 *
500 * WeakKeyMap contains one ST table which contains a pointer to the object as
501 * the key and the object as the value. This means that the key is of the type
502 * `VALUE *` while the value is of the type `VALUE`.
503 *
504 * The object is not directly stored as keys in the table because
505 * `rb_gc_mark_weak` requires a pointer to the memory location to overwrite
506 * when the object is reclaimed. Using a pointer into the ST table entry is not
507 * safe because the pointer can change when the ST table is resized.
508 *
509 * WeakKeyMap hashes and compares using the `#hash` and `#==` methods of the
510 * object, respectively.
511 *
512 * During GC and while iterating, reclaimed entries (i.e. the key points to
513 * `Qundef`) are removed from the ST table.
514 */
515
517 st_table *table;
518};
519
520static int
521wkmap_mark_table_i(st_data_t key, st_data_t val_obj, st_data_t _data)
522{
523 rb_gc_mark_movable((VALUE)val_obj);
524
525 return ST_CONTINUE;
526}
527
528static void
529wkmap_mark(void *ptr)
530{
531 struct weakkeymap *w = ptr;
532 if (w->table) {
533 st_foreach(w->table, wkmap_mark_table_i, (st_data_t)0);
534 }
535}
536
537static void
538wkmap_free(void *ptr)
539{
540 struct weakkeymap *w = ptr;
541
542 st_free_table(w->table);
543}
544
545static size_t
546wkmap_memsize(const void *ptr)
547{
548 const struct weakkeymap *w = ptr;
549
550 size_t size = 0;
551 if (w->table) {
552 size += st_memsize(w->table);
553 /* Each key of the table takes sizeof(VALUE) in size. */
554 size += st_table_size(w->table) * sizeof(VALUE);
555 }
556
557 return size;
558}
559
560static int
561wkmap_compact_table_i(st_data_t key, st_data_t val, st_data_t _data, int _error)
562{
563 if ((VALUE)key != rb_gc_location((VALUE)key) || (VALUE)val != rb_gc_location((VALUE)val)) {
564 return ST_REPLACE;
565 }
566
567 return ST_CONTINUE;
568}
569
570static int
571wkmap_compact_table_replace(st_data_t *key_ptr, st_data_t *val_ptr, st_data_t _data, int existing)
572{
573 RUBY_ASSERT(existing);
574
575 *key_ptr = (st_data_t)rb_gc_location((VALUE)*key_ptr);
576 *val_ptr = (st_data_t)rb_gc_location((VALUE)*val_ptr);
577
578 return ST_CONTINUE;
579}
580
581static void
582wkmap_compact(void *ptr)
583{
584 struct weakkeymap *w = ptr;
585
586 if (w->table) {
587 st_foreach_with_replace(w->table, wkmap_compact_table_i, wkmap_compact_table_replace, (st_data_t)0);
588 }
589}
590
591const rb_data_type_t rb_weakkeymap_type = {
592 "weakkeymap",
593 {
594 wkmap_mark,
595 wkmap_free,
596 wkmap_memsize,
597 wkmap_compact,
598 },
599 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE
600};
601
602static int
603wkmap_cmp(st_data_t x, st_data_t y)
604{
605 VALUE x_obj = (VALUE)x;
606 VALUE y_obj = (VALUE)y;
607
608 return rb_any_cmp(x_obj, y_obj);
609}
610
611static st_index_t
612wkmap_hash(st_data_t n)
613{
614 VALUE obj = (VALUE)n;
615
616 return rb_any_hash(obj);
617}
618
619static const struct st_hash_type wkmap_hash_type = {
620 wkmap_cmp,
621 wkmap_hash,
622};
623
624static int
625rb_wkmap_handle_weak_references_i(st_data_t key, st_data_t val, st_data_t arg)
626{
627 if (rb_gc_handle_weak_references_alive_p(key)) {
628 return ST_CONTINUE;
629 }
630 else {
631 return ST_DELETE;
632 }
633}
634
635void
636rb_wkmap_handle_weak_references(VALUE self)
637{
638 struct weakkeymap *w;
639 TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w);
640
641 st_foreach(w->table, rb_wkmap_handle_weak_references_i, (st_data_t)0);
642}
643
644static VALUE
645wkmap_allocate(VALUE klass)
646{
647 struct weakkeymap *w;
648
649 VALUE obj = TypedData_Make_Struct(klass, struct weakkeymap, &rb_weakkeymap_type, w);
650
651 w->table = st_init_table(&wkmap_hash_type);
652
653 rb_gc_declare_weak_references(obj);
654
655 return obj;
656}
657
658static VALUE
659wkmap_lookup(VALUE self, VALUE key)
660{
661 struct weakkeymap *w;
662 TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w);
663
664 st_data_t data;
665 if (!st_lookup(w->table, (st_data_t)key, &data)) return Qundef;
666
667 return (VALUE)data;
668}
669
670/*
671 * call-seq:
672 * map[key] -> value
673 *
674 * Returns the value associated with the given +key+ if found.
675 *
676 * If +key+ is not found, returns +nil+.
677 */
678static VALUE
679wkmap_aref(VALUE self, VALUE key)
680{
681 VALUE obj = wkmap_lookup(self, key);
682 return !UNDEF_P(obj) ? obj : Qnil;
683}
684
686 VALUE new_key;
687 VALUE new_val;
688};
689
690/*
691 * call-seq:
692 * map[key] = value -> value
693 *
694 * Associates the given +value+ with the given +key+
695 *
696 * The reference to +key+ is weak, so when there is no other reference
697 * to +key+ it may be garbage collected.
698 *
699 * If the given +key+ exists, replaces its value with the given +value+;
700 * the ordering is not affected
701 */
702static VALUE
703wkmap_aset(VALUE self, VALUE key, VALUE val)
704{
705 struct weakkeymap *w;
706 TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w);
707
708 if (!FL_ABLE(key) || SYMBOL_P(key) || RB_BIGNUM_TYPE_P(key) || RB_TYPE_P(key, T_FLOAT)) {
709 rb_raise(rb_eArgError, "WeakKeyMap keys must be garbage collectable");
711 }
712
713 st_insert(w->table, (st_data_t)key, (st_data_t)val);
714
715 RB_OBJ_WRITTEN(self, Qundef, key);
716 RB_OBJ_WRITTEN(self, Qundef, val);
717
718 return val;
719}
720
721/*
722 * call-seq:
723 * map.delete(key) -> value or nil
724 * map.delete(key) {|key| ... } -> object
725 *
726 * Deletes the entry for the given +key+ and returns its associated value.
727 *
728 * If no block is given and +key+ is found, deletes the entry and returns the associated value:
729 * m = ObjectSpace::WeakKeyMap.new
730 * key = "foo" # to hold reference to the key
731 * m[key] = 1
732 * m.delete("foo") # => 1
733 * m["foo"] # => nil
734 *
735 * If no block given and +key+ is not found, returns +nil+.
736 *
737 * If a block is given and +key+ is found, ignores the block,
738 * deletes the entry, and returns the associated value:
739 * m = ObjectSpace::WeakKeyMap.new
740 * key = "foo" # to hold reference to the key
741 * m[key] = 2
742 * m.delete("foo") { |key| raise 'Will never happen'} # => 2
743 *
744 * If a block is given and +key+ is not found,
745 * yields the +key+ to the block and returns the block's return value:
746 * m = ObjectSpace::WeakKeyMap.new
747 * m.delete("nosuch") { |key| "Key #{key} not found" } # => "Key nosuch not found"
748 */
749
750static VALUE
751wkmap_delete(VALUE self, VALUE key)
752{
753 struct weakkeymap *w;
754 TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w);
755
756 st_data_t orig_key = (st_data_t)key;
757 st_data_t orig_val;
758 if (st_delete(w->table, &orig_key, &orig_val)) {
759 return (VALUE)orig_val;
760 }
761
762 if (rb_block_given_p()) {
763 return rb_yield(key);
764 }
765 else {
766 return Qnil;
767 }
768}
769
770/*
771 * call-seq:
772 * map.getkey(key) -> existing_key or nil
773 *
774 * Returns the existing equal key if it exists, otherwise returns +nil+.
775 *
776 * This might be useful for implementing caches, so that only one copy of
777 * some object would be used everywhere in the program:
778 *
779 * value = {amount: 1, currency: 'USD'}
780 *
781 * # Now if we put this object in a cache:
782 * cache = ObjectSpace::WeakKeyMap.new
783 * cache[value] = true
784 *
785 * # ...we can always extract from there and use the same object:
786 * copy = cache.getkey({amount: 1, currency: 'USD'})
787 * copy.object_id == value.object_id #=> true
788 */
789static VALUE
790wkmap_getkey(VALUE self, VALUE key)
791{
792 struct weakkeymap *w;
793 TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w);
794
795 st_data_t orig_key;
796 if (!st_get_key(w->table, (st_data_t)key, &orig_key)) return Qnil;
797
798 return (VALUE)orig_key;
799}
800
801/*
802 * call-seq:
803 * map.key?(key) -> true or false
804 *
805 * Returns +true+ if +key+ is a key in +self+, otherwise +false+.
806 */
807static VALUE
808wkmap_has_key(VALUE self, VALUE key)
809{
810 return RBOOL(!UNDEF_P(wkmap_lookup(self, key)));
811}
812
813/*
814 * call-seq:
815 * map.clear -> self
816 *
817 * Removes all map entries; returns +self+.
818 */
819static VALUE
820wkmap_clear(VALUE self)
821{
822 struct weakkeymap *w;
823 TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w);
824
825 st_clear(w->table);
826
827 return self;
828}
829
830/*
831 * call-seq:
832 * map.inspect -> new_string
833 *
834 * Returns a new String containing informations about the map:
835 *
836 * m = ObjectSpace::WeakKeyMap.new
837 * m[key] = value
838 * m.inspect # => "#<ObjectSpace::WeakKeyMap:0x00000001028dcba8 size=1>"
839 *
840 */
841static VALUE
842wkmap_inspect(VALUE self)
843{
844 struct weakkeymap *w;
845 TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w);
846
847 st_index_t n = st_table_size(w->table);
848
849#if SIZEOF_ST_INDEX_T <= SIZEOF_LONG
850 const char * format = "#<%"PRIsVALUE":%p size=%lu>";
851#else
852 const char * format = "#<%"PRIsVALUE":%p size=%llu>";
853#endif
854
855 VALUE str = rb_sprintf(format, rb_class_name(CLASS_OF(self)), (void *)self, n);
856 return str;
857}
858
859/*
860 * Document-class: ObjectSpace::WeakMap
861 *
862 * An ObjectSpace::WeakMap is a key-value map that holds weak references
863 * to its keys and values, so they can be garbage-collected when there are
864 * no more references left.
865 *
866 * Keys in the map are compared by identity.
867 *
868 * m = ObjectSpace::WeakMap.new
869 * key1 = "foo"
870 * val1 = Object.new
871 * m[key1] = val1
872 *
873 * key2 = "bar"
874 * val2 = Object.new
875 * m[key2] = val2
876 *
877 * m[key1] #=> #<Object:0x0...>
878 * m[key2] #=> #<Object:0x0...>
879 *
880 * val1 = nil # remove the other reference to value
881 * GC.start
882 *
883 * m[key1] #=> nil
884 * m.keys #=> ["bar"]
885 *
886 * key2 = nil # remove the other reference to key
887 * GC.start
888 *
889 * m[key2] #=> nil
890 * m.keys #=> []
891 *
892 * (Note that GC.start is used here only for demonstrational purposes and might
893 * not always lead to demonstrated results.)
894 *
895 *
896 * See also ObjectSpace::WeakKeyMap map class, which compares keys by value,
897 * and holds weak references only to the keys.
898 */
899
900/*
901 * Document-class: ObjectSpace::WeakKeyMap
902 *
903 * An ObjectSpace::WeakKeyMap is a key-value map that holds weak references
904 * to its keys, so they can be garbage collected when there is no more references.
905 *
906 * Unlike ObjectSpace::WeakMap:
907 *
908 * * references to values are _strong_, so they aren't garbage collected while
909 * they are in the map;
910 * * keys are compared by value (using Object#eql?), not by identity;
911 * * only garbage-collectable objects can be used as keys.
912 *
913 * map = ObjectSpace::WeakKeyMap.new
914 * val = Time.new(2023, 12, 7)
915 * key = "name"
916 * map[key] = val
917 *
918 * # Value is fetched by equality: the instance of string "name" is
919 * # different here, but it is equal to the key
920 * map["name"] #=> 2023-12-07 00:00:00 +0200
921 *
922 * val = nil
923 * GC.start
924 * # There are no more references to `val`, yet the pair isn't
925 * # garbage-collected.
926 * map["name"] #=> 2023-12-07 00:00:00 +0200
927 *
928 * key = nil
929 * GC.start
930 * # There are no more references to `key`, key and value are
931 * # garbage-collected.
932 * map["name"] #=> nil
933 *
934 * (Note that GC.start is used here only for demonstrational purposes and might
935 * not always lead to demonstrated results.)
936 *
937 * The collection is especially useful for implementing caches of lightweight value
938 * objects, so that only one copy of each value representation would be stored in
939 * memory, but the copies that aren't used would be garbage-collected.
940 *
941 * CACHE = ObjectSpace::WeakKeyMap
942 *
943 * def make_value(**)
944 * val = ValueObject.new(**)
945 * if (existing = @cache.getkey(val))
946 * # if the object with this value exists, we return it
947 * existing
948 * else
949 * # otherwise, put it in the cache
950 * @cache[val] = true
951 * val
952 * end
953 * end
954 *
955 * This will result in +make_value+ returning the same object for same set of attributes
956 * always, but the values that aren't needed anymore wouldn't be sitting in the cache forever.
957 */
958
959void
960Init_WeakMap(void)
961{
962 VALUE rb_mObjectSpace = rb_define_module("ObjectSpace");
963
964 VALUE rb_cWeakMap = rb_define_class_under(rb_mObjectSpace, "WeakMap", rb_cObject);
965 rb_define_alloc_func(rb_cWeakMap, wmap_allocate);
966 rb_define_method(rb_cWeakMap, "[]=", wmap_aset, 2);
967 rb_define_method(rb_cWeakMap, "[]", wmap_aref, 1);
968 rb_define_method(rb_cWeakMap, "delete", wmap_delete, 1);
969 rb_define_method(rb_cWeakMap, "include?", wmap_has_key, 1);
970 rb_define_method(rb_cWeakMap, "member?", wmap_has_key, 1);
971 rb_define_method(rb_cWeakMap, "key?", wmap_has_key, 1);
972 rb_define_method(rb_cWeakMap, "inspect", wmap_inspect, 0);
973 rb_define_method(rb_cWeakMap, "each", wmap_each, 0);
974 rb_define_method(rb_cWeakMap, "each_pair", wmap_each, 0);
975 rb_define_method(rb_cWeakMap, "each_key", wmap_each_key, 0);
976 rb_define_method(rb_cWeakMap, "each_value", wmap_each_value, 0);
977 rb_define_method(rb_cWeakMap, "keys", wmap_keys, 0);
978 rb_define_method(rb_cWeakMap, "values", wmap_values, 0);
979 rb_define_method(rb_cWeakMap, "size", wmap_size, 0);
980 rb_define_method(rb_cWeakMap, "length", wmap_size, 0);
981 rb_include_module(rb_cWeakMap, rb_mEnumerable);
982
983 VALUE rb_cWeakKeyMap = rb_define_class_under(rb_mObjectSpace, "WeakKeyMap", rb_cObject);
984 rb_define_alloc_func(rb_cWeakKeyMap, wkmap_allocate);
985 rb_define_method(rb_cWeakKeyMap, "[]=", wkmap_aset, 2);
986 rb_define_method(rb_cWeakKeyMap, "[]", wkmap_aref, 1);
987 rb_define_method(rb_cWeakKeyMap, "delete", wkmap_delete, 1);
988 rb_define_method(rb_cWeakKeyMap, "getkey", wkmap_getkey, 1);
989 rb_define_method(rb_cWeakKeyMap, "key?", wkmap_has_key, 1);
990 rb_define_method(rb_cWeakKeyMap, "clear", wkmap_clear, 0);
991 rb_define_method(rb_cWeakKeyMap, "inspect", wkmap_inspect, 0);
992}
#define RUBY_ASSERT(...)
Asserts that the given expression is truthy if and only if RUBY_DEBUG is truthy.
Definition assert.h:219
#define rb_define_method(klass, mid, func, arity)
Defines klass#mid.
void rb_include_module(VALUE klass, VALUE module)
Includes a module to a class.
Definition class.c:1798
VALUE rb_define_class_under(VALUE outer, const char *name, VALUE super)
Defines a class under the namespace of outer.
Definition class.c:1622
VALUE rb_define_module(const char *name)
Defines a top-level module.
Definition class.c:1704
int rb_block_given_p(void)
Determines if the current method is given a block.
Definition eval.c:1010
#define Qundef
Old name of RUBY_Qundef.
#define rb_str_cat2
Old name of rb_str_cat_cstr.
Definition string.h:1682
#define T_FLOAT
Old name of RUBY_T_FLOAT.
Definition value_type.h:64
#define SPECIAL_CONST_P
Old name of RB_SPECIAL_CONST_P.
#define ULONG2NUM
Old name of RB_ULONG2NUM.
Definition long.h:60
#define UNREACHABLE_RETURN
Old name of RBIMPL_UNREACHABLE_RETURN.
Definition assume.h:29
#define CLASS_OF
Old name of rb_class_of.
Definition globals.h:205
#define FL_ABLE
Old name of RB_FL_ABLE.
Definition fl_type.h:120
#define ULL2NUM
Old name of RB_ULL2NUM.
Definition long_long.h:31
#define Qnil
Old name of RUBY_Qnil.
#define SYMBOL_P
Old name of RB_SYMBOL_P.
Definition value_type.h:88
VALUE rb_any_to_s(VALUE obj)
Generates a textual representation of the given object.
Definition object.c:675
VALUE rb_mEnumerable
Enumerable module.
Definition enum.c:27
VALUE rb_inspect(VALUE obj)
Generates a human-readable textual representation of the given object.
Definition object.c:686
#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:615
VALUE rb_ary_new(void)
Allocates a new, empty array.
VALUE rb_ary_push(VALUE ary, VALUE elem)
Special case of rb_ary_cat() that it adds only one element.
VALUE rb_str_append(VALUE dst, VALUE src)
Identical to rb_str_buf_append(), except it converts the right hand side before concatenating.
Definition string.c:3798
VALUE rb_class_name(VALUE obj)
Queries the name of the given object's class.
Definition variable.c:500
void rb_define_alloc_func(VALUE klass, rb_alloc_func_t func)
Sets the allocator function of a class.
VALUE rb_yield_values(int n,...)
Identical to rb_yield(), except it takes variadic number of parameters and pass them to the block.
Definition vm_eval.c:1395
VALUE rb_yield(VALUE val)
Yields the block.
Definition vm_eval.c:1372
#define TypedData_Get_Struct(obj, type, data_type, sval)
Obtains a C struct from inside of a wrapper Ruby object.
Definition rtypeddata.h:724
#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:542
#define _(args)
This was a transition path from K&R to ANSI.
Definition stdarg.h:35
This is the struct that holds necessary info for a struct.
Definition rtypeddata.h:208
Definition st.h:79
Definition weakmap.c:32
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