diff --git a/common.mk b/common.mk index 1195f81b8a..e224bf28a7 100644 --- a/common.mk +++ b/common.mk @@ -150,6 +150,7 @@ COMMONOBJS = array.$(OBJEXT) \ vm_dump.$(OBJEXT) \ vm_sync.$(OBJEXT) \ vm_trace.$(OBJEXT) \ + weakmap.$(OBJEXT) \ $(YJIT_OBJ) \ $(YJIT_LIBOBJ) \ $(COROUTINE_OBJ) \ @@ -17999,6 +18000,170 @@ vm_trace.$(OBJEXT): {$(VPATH)}vm_core.h vm_trace.$(OBJEXT): {$(VPATH)}vm_opts.h vm_trace.$(OBJEXT): {$(VPATH)}vm_trace.c vm_trace.$(OBJEXT): {$(VPATH)}yjit.h +weakmap.$(OBJEXT): $(hdrdir)/ruby/ruby.h +weakmap.$(OBJEXT): $(top_srcdir)/internal/compilers.h +weakmap.$(OBJEXT): $(top_srcdir)/internal/gc.h +weakmap.$(OBJEXT): $(top_srcdir)/internal/hash.h +weakmap.$(OBJEXT): $(top_srcdir)/internal/proc.h +weakmap.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h +weakmap.$(OBJEXT): $(top_srcdir)/internal/warnings.h +weakmap.$(OBJEXT): {$(VPATH)}assert.h +weakmap.$(OBJEXT): {$(VPATH)}backward/2/assume.h +weakmap.$(OBJEXT): {$(VPATH)}backward/2/attributes.h +weakmap.$(OBJEXT): {$(VPATH)}backward/2/bool.h +weakmap.$(OBJEXT): {$(VPATH)}backward/2/gcc_version_since.h +weakmap.$(OBJEXT): {$(VPATH)}backward/2/inttypes.h +weakmap.$(OBJEXT): {$(VPATH)}backward/2/limits.h +weakmap.$(OBJEXT): {$(VPATH)}backward/2/long_long.h +weakmap.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h +weakmap.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h +weakmap.$(OBJEXT): {$(VPATH)}config.h +weakmap.$(OBJEXT): {$(VPATH)}defines.h +weakmap.$(OBJEXT): {$(VPATH)}intern.h +weakmap.$(OBJEXT): {$(VPATH)}internal.h +weakmap.$(OBJEXT): {$(VPATH)}internal/abi.h +weakmap.$(OBJEXT): {$(VPATH)}internal/anyargs.h +weakmap.$(OBJEXT): {$(VPATH)}internal/arithmetic.h +weakmap.$(OBJEXT): {$(VPATH)}internal/arithmetic/char.h +weakmap.$(OBJEXT): {$(VPATH)}internal/arithmetic/double.h +weakmap.$(OBJEXT): {$(VPATH)}internal/arithmetic/fixnum.h +weakmap.$(OBJEXT): {$(VPATH)}internal/arithmetic/gid_t.h +weakmap.$(OBJEXT): {$(VPATH)}internal/arithmetic/int.h +weakmap.$(OBJEXT): {$(VPATH)}internal/arithmetic/intptr_t.h +weakmap.$(OBJEXT): {$(VPATH)}internal/arithmetic/long.h +weakmap.$(OBJEXT): {$(VPATH)}internal/arithmetic/long_long.h +weakmap.$(OBJEXT): {$(VPATH)}internal/arithmetic/mode_t.h +weakmap.$(OBJEXT): {$(VPATH)}internal/arithmetic/off_t.h +weakmap.$(OBJEXT): {$(VPATH)}internal/arithmetic/pid_t.h +weakmap.$(OBJEXT): {$(VPATH)}internal/arithmetic/short.h +weakmap.$(OBJEXT): {$(VPATH)}internal/arithmetic/size_t.h +weakmap.$(OBJEXT): {$(VPATH)}internal/arithmetic/st_data_t.h +weakmap.$(OBJEXT): {$(VPATH)}internal/arithmetic/uid_t.h +weakmap.$(OBJEXT): {$(VPATH)}internal/assume.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/alloc_size.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/artificial.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/cold.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/const.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/constexpr.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/deprecated.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/diagnose_if.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/enum_extensibility.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/error.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/flag_enum.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/forceinline.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/format.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/maybe_unused.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/noalias.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/nodiscard.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/noexcept.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/noinline.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/nonnull.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/noreturn.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/packed_struct.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/pure.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/restrict.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/returns_nonnull.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/warning.h +weakmap.$(OBJEXT): {$(VPATH)}internal/attr/weakref.h +weakmap.$(OBJEXT): {$(VPATH)}internal/cast.h +weakmap.$(OBJEXT): {$(VPATH)}internal/compiler_is.h +weakmap.$(OBJEXT): {$(VPATH)}internal/compiler_is/apple.h +weakmap.$(OBJEXT): {$(VPATH)}internal/compiler_is/clang.h +weakmap.$(OBJEXT): {$(VPATH)}internal/compiler_is/gcc.h +weakmap.$(OBJEXT): {$(VPATH)}internal/compiler_is/intel.h +weakmap.$(OBJEXT): {$(VPATH)}internal/compiler_is/msvc.h +weakmap.$(OBJEXT): {$(VPATH)}internal/compiler_is/sunpro.h +weakmap.$(OBJEXT): {$(VPATH)}internal/compiler_since.h +weakmap.$(OBJEXT): {$(VPATH)}internal/config.h +weakmap.$(OBJEXT): {$(VPATH)}internal/constant_p.h +weakmap.$(OBJEXT): {$(VPATH)}internal/core.h +weakmap.$(OBJEXT): {$(VPATH)}internal/core/rarray.h +weakmap.$(OBJEXT): {$(VPATH)}internal/core/rbasic.h +weakmap.$(OBJEXT): {$(VPATH)}internal/core/rbignum.h +weakmap.$(OBJEXT): {$(VPATH)}internal/core/rclass.h +weakmap.$(OBJEXT): {$(VPATH)}internal/core/rdata.h +weakmap.$(OBJEXT): {$(VPATH)}internal/core/rfile.h +weakmap.$(OBJEXT): {$(VPATH)}internal/core/rhash.h +weakmap.$(OBJEXT): {$(VPATH)}internal/core/robject.h +weakmap.$(OBJEXT): {$(VPATH)}internal/core/rregexp.h +weakmap.$(OBJEXT): {$(VPATH)}internal/core/rstring.h +weakmap.$(OBJEXT): {$(VPATH)}internal/core/rstruct.h +weakmap.$(OBJEXT): {$(VPATH)}internal/core/rtypeddata.h +weakmap.$(OBJEXT): {$(VPATH)}internal/ctype.h +weakmap.$(OBJEXT): {$(VPATH)}internal/dllexport.h +weakmap.$(OBJEXT): {$(VPATH)}internal/dosish.h +weakmap.$(OBJEXT): {$(VPATH)}internal/error.h +weakmap.$(OBJEXT): {$(VPATH)}internal/eval.h +weakmap.$(OBJEXT): {$(VPATH)}internal/event.h +weakmap.$(OBJEXT): {$(VPATH)}internal/fl_type.h +weakmap.$(OBJEXT): {$(VPATH)}internal/gc.h +weakmap.$(OBJEXT): {$(VPATH)}internal/glob.h +weakmap.$(OBJEXT): {$(VPATH)}internal/globals.h +weakmap.$(OBJEXT): {$(VPATH)}internal/has/attribute.h +weakmap.$(OBJEXT): {$(VPATH)}internal/has/builtin.h +weakmap.$(OBJEXT): {$(VPATH)}internal/has/c_attribute.h +weakmap.$(OBJEXT): {$(VPATH)}internal/has/cpp_attribute.h +weakmap.$(OBJEXT): {$(VPATH)}internal/has/declspec_attribute.h +weakmap.$(OBJEXT): {$(VPATH)}internal/has/extension.h +weakmap.$(OBJEXT): {$(VPATH)}internal/has/feature.h +weakmap.$(OBJEXT): {$(VPATH)}internal/has/warning.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/array.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/bignum.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/class.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/compar.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/complex.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/cont.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/dir.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/enum.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/enumerator.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/error.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/eval.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/file.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/hash.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/io.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/load.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/marshal.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/numeric.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/object.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/parse.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/proc.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/process.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/random.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/range.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/rational.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/re.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/select.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/signal.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/string.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/struct.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/thread.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/time.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/variable.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/vm.h +weakmap.$(OBJEXT): {$(VPATH)}internal/interpreter.h +weakmap.$(OBJEXT): {$(VPATH)}internal/iterator.h +weakmap.$(OBJEXT): {$(VPATH)}internal/memory.h +weakmap.$(OBJEXT): {$(VPATH)}internal/method.h +weakmap.$(OBJEXT): {$(VPATH)}internal/module.h +weakmap.$(OBJEXT): {$(VPATH)}internal/newobj.h +weakmap.$(OBJEXT): {$(VPATH)}internal/scan_args.h +weakmap.$(OBJEXT): {$(VPATH)}internal/special_consts.h +weakmap.$(OBJEXT): {$(VPATH)}internal/static_assert.h +weakmap.$(OBJEXT): {$(VPATH)}internal/stdalign.h +weakmap.$(OBJEXT): {$(VPATH)}internal/stdbool.h +weakmap.$(OBJEXT): {$(VPATH)}internal/symbol.h +weakmap.$(OBJEXT): {$(VPATH)}internal/value.h +weakmap.$(OBJEXT): {$(VPATH)}internal/value_type.h +weakmap.$(OBJEXT): {$(VPATH)}internal/variable.h +weakmap.$(OBJEXT): {$(VPATH)}internal/warning_push.h +weakmap.$(OBJEXT): {$(VPATH)}internal/xmalloc.h +weakmap.$(OBJEXT): {$(VPATH)}missing.h +weakmap.$(OBJEXT): {$(VPATH)}st.h +weakmap.$(OBJEXT): {$(VPATH)}subst.h +weakmap.$(OBJEXT): {$(VPATH)}weakmap.c yjit.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h yjit.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h yjit.$(OBJEXT): $(CCAN_DIR)/list/list.h diff --git a/gc.c b/gc.c index bbcdc4cedc..fdb6334a9d 100644 --- a/gc.c +++ b/gc.c @@ -1183,10 +1183,8 @@ total_freed_pages(rb_objspace_t *objspace) #define is_lazy_sweeping(objspace) (GC_ENABLE_LAZY_SWEEP && has_sweeping_pages(objspace)) #if SIZEOF_LONG == SIZEOF_VOIDP -# define nonspecial_obj_id(obj) (VALUE)((SIGNED_VALUE)(obj)|FIXNUM_FLAG) # define obj_id_to_ref(objid) ((objid) ^ FIXNUM_FLAG) /* unset FIXNUM_FLAG */ #elif SIZEOF_LONG_LONG == SIZEOF_VOIDP -# define nonspecial_obj_id(obj) LL2NUM((SIGNED_VALUE)(obj) / 2) # define obj_id_to_ref(objid) (FIXNUM_P(objid) ? \ ((objid) ^ FIXNUM_FLAG) : (NUM2PTR(objid) << 1)) #else @@ -1220,8 +1218,6 @@ void rb_vm_update_references(void *ptr); void rb_gcdebug_print_obj_condition(VALUE obj); -static VALUE define_final0(VALUE obj, VALUE block); - NORETURN(static void *gc_vraise(void *ptr)); NORETURN(static void gc_raise(VALUE exc, const char *fmt, ...)); NORETURN(static void negative_size_allocation_error(const char *)); @@ -4235,6 +4231,45 @@ should_be_finalizable(VALUE obj) rb_check_frozen(obj); } +VALUE +rb_define_finalizer_no_check(VALUE obj, VALUE block) +{ + rb_objspace_t *objspace = &rb_objspace; + VALUE table; + st_data_t data; + + RBASIC(obj)->flags |= FL_FINALIZE; + + if (st_lookup(finalizer_table, obj, &data)) { + table = (VALUE)data; + + /* avoid duplicate block, table is usually small */ + { + long len = RARRAY_LEN(table); + long i; + + for (i = 0; i < len; i++) { + VALUE recv = RARRAY_AREF(table, i); + if (rb_equal(recv, block)) { + block = recv; + goto end; + } + } + } + + rb_ary_push(table, block); + } + else { + table = rb_ary_new3(1, block); + RBASIC_CLEAR_CLASS(table); + st_add_direct(finalizer_table, obj, table); + } + end: + block = rb_ary_new3(2, INT2FIX(0), block); + OBJ_FREEZE(block); + return block; +} + /* * call-seq: * ObjectSpace.define_finalizer(obj, aProc=proc()) @@ -4315,46 +4350,7 @@ define_final(int argc, VALUE *argv, VALUE os) rb_warn("finalizer references object to be finalized"); } - return define_final0(obj, block); -} - -static VALUE -define_final0(VALUE obj, VALUE block) -{ - rb_objspace_t *objspace = &rb_objspace; - VALUE table; - st_data_t data; - - RBASIC(obj)->flags |= FL_FINALIZE; - - if (st_lookup(finalizer_table, obj, &data)) { - table = (VALUE)data; - - /* avoid duplicate block, table is usually small */ - { - long len = RARRAY_LEN(table); - long i; - - for (i = 0; i < len; i++) { - VALUE recv = RARRAY_AREF(table, i); - if (rb_equal(recv, block)) { - block = recv; - goto end; - } - } - } - - rb_ary_push(table, block); - } - else { - table = rb_ary_new3(1, block); - RBASIC_CLEAR_CLASS(table); - st_add_direct(finalizer_table, obj, table); - } - end: - block = rb_ary_new3(2, INT2FIX(0), block); - OBJ_FREEZE(block); - return block; + return rb_define_finalizer_no_check(obj, block); } VALUE @@ -4362,7 +4358,7 @@ rb_define_finalizer(VALUE obj, VALUE block) { should_be_finalizable(obj); should_be_callable(block); - return define_final0(obj, block); + return rb_define_finalizer_no_check(obj, block); } void @@ -4688,9 +4684,18 @@ rb_objspace_garbage_object_p(VALUE obj) return is_garbage_object(objspace, obj); } -static VALUE -id2ref_obj_tbl(rb_objspace_t *objspace, VALUE objid) +bool +rb_gc_is_ptr_to_obj(void *ptr) { + rb_objspace_t *objspace = &rb_objspace; + return is_pointer_to_heap(objspace, ptr); +} + +VALUE +rb_gc_id2ref_obj_tbl(VALUE objid) +{ + rb_objspace_t *objspace = &rb_objspace; + VALUE orig; if (st_lookup(objspace->id_to_obj_tbl, objid, &orig)) { return orig; @@ -4747,7 +4752,7 @@ id2ref(VALUE objid) } } - if (!UNDEF_P(orig = id2ref_obj_tbl(objspace, objid)) && + if (!UNDEF_P(orig = rb_gc_id2ref_obj_tbl(objid)) && is_live_object(objspace, orig)) { if (!rb_multi_ractor_p() || rb_ractor_shareable_p(orig)) { @@ -4821,16 +4826,21 @@ cached_object_id(VALUE obj) } static VALUE -nonspecial_obj_id_(VALUE obj) +nonspecial_obj_id(VALUE obj) { - return nonspecial_obj_id(obj); +#if SIZEOF_LONG == SIZEOF_VOIDP + return (VALUE)((SIGNED_VALUE)(obj)|FIXNUM_FLAG); +#elif SIZEOF_LONG_LONG == SIZEOF_VOIDP + return LL2NUM((SIGNED_VALUE)(obj) / 2); +#else +# error not supported +#endif } - VALUE rb_memory_id(VALUE obj) { - return rb_find_object_id(obj, nonspecial_obj_id_); + return rb_find_object_id(obj, nonspecial_obj_id); } /* @@ -12868,787 +12878,6 @@ rb_gc_adjust_memory_usage(ssize_t diff) } } -/* - ------------------------------ WeakMap ------------------------------ -*/ - -struct weakmap { - st_table *obj2wmap; /* obj -> [ref,...] */ - st_table *wmap2obj; /* ref -> obj */ - VALUE final; -}; - -#define WMAP_DELETE_DEAD_OBJECT_IN_MARK 0 - -#if WMAP_DELETE_DEAD_OBJECT_IN_MARK -static int -wmap_mark_map(st_data_t key, st_data_t val, st_data_t arg) -{ - rb_objspace_t *objspace = (rb_objspace_t *)arg; - VALUE obj = (VALUE)val; - if (!is_live_object(objspace, obj)) return ST_DELETE; - return ST_CONTINUE; -} -#endif - -static void -wmap_compact(void *ptr) -{ - struct weakmap *w = ptr; - if (w->wmap2obj) rb_gc_update_tbl_refs(w->wmap2obj); - if (w->obj2wmap) rb_gc_update_tbl_refs(w->obj2wmap); - w->final = rb_gc_location(w->final); -} - -static void -wmap_mark(void *ptr) -{ - struct weakmap *w = ptr; -#if WMAP_DELETE_DEAD_OBJECT_IN_MARK - if (w->obj2wmap) st_foreach(w->obj2wmap, wmap_mark_map, (st_data_t)&rb_objspace); -#endif - rb_gc_mark_movable(w->final); -} - -static int -wmap_free_map(st_data_t key, st_data_t val, st_data_t arg) -{ - VALUE *ptr = (VALUE *)val; - ruby_sized_xfree(ptr, (ptr[0] + 1) * sizeof(VALUE)); - return ST_CONTINUE; -} - -static void -wmap_free(void *ptr) -{ - struct weakmap *w = ptr; - st_foreach(w->obj2wmap, wmap_free_map, 0); - st_free_table(w->obj2wmap); - st_free_table(w->wmap2obj); - xfree(w); -} - -static int -wmap_memsize_map(st_data_t key, st_data_t val, st_data_t arg) -{ - VALUE *ptr = (VALUE *)val; - *(size_t *)arg += (ptr[0] + 1) * sizeof(VALUE); - return ST_CONTINUE; -} - -static size_t -wmap_memsize(const void *ptr) -{ - size_t size; - const struct weakmap *w = ptr; - size = sizeof(*w); - size += st_memsize(w->obj2wmap); - size += st_memsize(w->wmap2obj); - st_foreach(w->obj2wmap, wmap_memsize_map, (st_data_t)&size); - return size; -} - -static const rb_data_type_t weakmap_type = { - "weakmap", - { - wmap_mark, - wmap_free, - wmap_memsize, - wmap_compact, - }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY -}; - -static VALUE wmap_finalize(RB_BLOCK_CALL_FUNC_ARGLIST(objid, self)); - -static VALUE -wmap_allocate(VALUE klass) -{ - struct weakmap *w; - VALUE obj = TypedData_Make_Struct(klass, struct weakmap, &weakmap_type, w); - w->obj2wmap = rb_init_identtable(); - w->wmap2obj = rb_init_identtable(); - w->final = rb_func_lambda_new(wmap_finalize, obj, 1, 1); - return obj; -} - -static int -wmap_live_p(rb_objspace_t *objspace, VALUE obj) -{ - if (SPECIAL_CONST_P(obj)) return TRUE; - /* If is_pointer_to_heap returns false, the page could be in the tomb heap - * or have already been freed. */ - if (!is_pointer_to_heap(objspace, (void *)obj)) return FALSE; - - void *poisoned = asan_unpoison_object_temporary(obj); - - enum ruby_value_type t = BUILTIN_TYPE(obj); - int ret = (!(t == T_NONE || t >= T_FIXNUM || t == T_ICLASS) && - is_live_object(objspace, obj)); - - if (poisoned) { - asan_poison_object(obj); - } - - return ret; -} - -static int -wmap_final_func(st_data_t *key, st_data_t *value, st_data_t arg, int existing) -{ - VALUE wmap, *ptr, size, i, j; - if (!existing) return ST_STOP; - wmap = (VALUE)arg, ptr = (VALUE *)*value; - for (i = j = 1, size = ptr[0]; i <= size; ++i) { - if (ptr[i] != wmap) { - ptr[j++] = ptr[i]; - } - } - if (j == 1) { - ruby_sized_xfree(ptr, i * sizeof(VALUE)); - return ST_DELETE; - } - if (j < i) { - SIZED_REALLOC_N(ptr, VALUE, j + 1, i); - ptr[0] = j; - *value = (st_data_t)ptr; - } - return ST_CONTINUE; -} - -/* :nodoc: */ -static VALUE -wmap_finalize(RB_BLOCK_CALL_FUNC_ARGLIST(objid, self)) -{ - st_data_t orig, wmap, data; - VALUE obj, *rids, i, size; - struct weakmap *w; - - TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); - /* Get reference from object id. */ - if (UNDEF_P(obj = id2ref_obj_tbl(&rb_objspace, objid))) { - rb_bug("wmap_finalize: objid is not found."); - } - - /* obj is original referenced object and/or weak reference. */ - orig = (st_data_t)obj; - if (st_delete(w->obj2wmap, &orig, &data)) { - rids = (VALUE *)data; - size = *rids++; - for (i = 0; i < size; ++i) { - wmap = (st_data_t)rids[i]; - st_delete(w->wmap2obj, &wmap, NULL); - } - ruby_sized_xfree((VALUE *)data, (size + 1) * sizeof(VALUE)); - } - - wmap = (st_data_t)obj; - if (st_delete(w->wmap2obj, &wmap, &orig)) { - wmap = (st_data_t)obj; - st_update(w->obj2wmap, orig, wmap_final_func, wmap); - } - return self; -} - -struct wmap_iter_arg { - rb_objspace_t *objspace; - VALUE value; -}; - -static VALUE -wmap_inspect_append(rb_objspace_t *objspace, VALUE str, VALUE obj) -{ - if (SPECIAL_CONST_P(obj)) { - return rb_str_append(str, rb_inspect(obj)); - } - else if (wmap_live_p(objspace, obj)) { - return rb_str_append(str, rb_any_to_s(obj)); - } - else { - return rb_str_catf(str, "#", (void*)obj); - } -} - -static int -wmap_inspect_i(st_data_t key, st_data_t val, st_data_t arg) -{ - struct wmap_iter_arg *argp = (struct wmap_iter_arg *)arg; - rb_objspace_t *objspace = argp->objspace; - VALUE str = argp->value; - VALUE k = (VALUE)key, v = (VALUE)val; - - if (RSTRING_PTR(str)[0] == '#') { - rb_str_cat2(str, ", "); - } - else { - rb_str_cat2(str, ": "); - RSTRING_PTR(str)[0] = '#'; - } - wmap_inspect_append(objspace, str, k); - rb_str_cat2(str, " => "); - wmap_inspect_append(objspace, str, v); - - return ST_CONTINUE; -} - -static VALUE -wmap_inspect(VALUE self) -{ - VALUE str; - VALUE c = rb_class_name(CLASS_OF(self)); - struct weakmap *w; - struct wmap_iter_arg args; - - TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); - str = rb_sprintf("-<%"PRIsVALUE":%p", c, (void *)self); - if (w->wmap2obj) { - args.objspace = &rb_objspace; - args.value = str; - st_foreach(w->wmap2obj, wmap_inspect_i, (st_data_t)&args); - } - RSTRING_PTR(str)[0] = '#'; - rb_str_cat2(str, ">"); - return str; -} - -static inline bool -wmap_live_entry_p(rb_objspace_t *objspace, st_data_t key, st_data_t val) -{ - return wmap_live_p(objspace, (VALUE)key) && wmap_live_p(objspace, (VALUE)val); -} - -static int -wmap_each_i(st_data_t key, st_data_t val, st_data_t arg) -{ - rb_objspace_t *objspace = (rb_objspace_t *)arg; - - if (wmap_live_entry_p(objspace, key, val)) { - rb_yield_values(2, (VALUE)key, (VALUE)val); - return ST_CONTINUE; - } - else { - return ST_DELETE; - } -} - -/* Iterates over keys and objects in a weakly referenced object */ -static VALUE -wmap_each(VALUE self) -{ - struct weakmap *w; - rb_objspace_t *objspace = &rb_objspace; - - TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); - st_foreach(w->wmap2obj, wmap_each_i, (st_data_t)objspace); - return self; -} - -static int -wmap_each_key_i(st_data_t key, st_data_t val, st_data_t arg) -{ - rb_objspace_t *objspace = (rb_objspace_t *)arg; - - if (wmap_live_entry_p(objspace, key, val)) { - rb_yield((VALUE)key); - return ST_CONTINUE; - } - else { - return ST_DELETE; - } -} - -/* Iterates over keys and objects in a weakly referenced object */ -static VALUE -wmap_each_key(VALUE self) -{ - struct weakmap *w; - rb_objspace_t *objspace = &rb_objspace; - - TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); - st_foreach(w->wmap2obj, wmap_each_key_i, (st_data_t)objspace); - return self; -} - -static int -wmap_each_value_i(st_data_t key, st_data_t val, st_data_t arg) -{ - rb_objspace_t *objspace = (rb_objspace_t *)arg; - - if (wmap_live_entry_p(objspace, key, val)) { - rb_yield((VALUE)val); - return ST_CONTINUE; - } - else { - return ST_DELETE; - } -} - -/* Iterates over keys and objects in a weakly referenced object */ -static VALUE -wmap_each_value(VALUE self) -{ - struct weakmap *w; - rb_objspace_t *objspace = &rb_objspace; - - TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); - st_foreach(w->wmap2obj, wmap_each_value_i, (st_data_t)objspace); - return self; -} - -static int -wmap_keys_i(st_data_t key, st_data_t val, st_data_t arg) -{ - struct wmap_iter_arg *argp = (struct wmap_iter_arg *)arg; - rb_objspace_t *objspace = argp->objspace; - VALUE ary = argp->value; - - if (wmap_live_entry_p(objspace, key, val)) { - rb_ary_push(ary, (VALUE)key); - return ST_CONTINUE; - } - else { - return ST_DELETE; - } -} - -/* Iterates over keys and objects in a weakly referenced object */ -static VALUE -wmap_keys(VALUE self) -{ - struct weakmap *w; - struct wmap_iter_arg args; - - TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); - args.objspace = &rb_objspace; - args.value = rb_ary_new(); - st_foreach(w->wmap2obj, wmap_keys_i, (st_data_t)&args); - return args.value; -} - -static int -wmap_values_i(st_data_t key, st_data_t val, st_data_t arg) -{ - struct wmap_iter_arg *argp = (struct wmap_iter_arg *)arg; - rb_objspace_t *objspace = argp->objspace; - VALUE ary = argp->value; - - if (wmap_live_entry_p(objspace, key, val)) { - rb_ary_push(ary, (VALUE)val); - return ST_CONTINUE; - } - else { - return ST_DELETE; - } -} - -/* Iterates over values and objects in a weakly referenced object */ -static VALUE -wmap_values(VALUE self) -{ - struct weakmap *w; - struct wmap_iter_arg args; - - TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); - args.objspace = &rb_objspace; - args.value = rb_ary_new(); - st_foreach(w->wmap2obj, wmap_values_i, (st_data_t)&args); - return args.value; -} - -static int -wmap_aset_update(st_data_t *key, st_data_t *val, st_data_t arg, int existing) -{ - VALUE size, *ptr, *optr; - if (existing) { - size = (ptr = optr = (VALUE *)*val)[0]; - ++size; - SIZED_REALLOC_N(ptr, VALUE, size + 1, size); - } - else { - optr = 0; - size = 1; - ptr = ruby_xmalloc0(2 * sizeof(VALUE)); - } - ptr[0] = size; - ptr[size] = (VALUE)arg; - if (ptr == optr) return ST_STOP; - *val = (st_data_t)ptr; - return ST_CONTINUE; -} - -/* Creates a weak reference from the given key to the given value */ -static VALUE -wmap_aset(VALUE self, VALUE key, VALUE value) -{ - struct weakmap *w; - - TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); - if (FL_ABLE(value)) { - define_final0(value, w->final); - } - if (FL_ABLE(key)) { - define_final0(key, w->final); - } - - st_update(w->obj2wmap, (st_data_t)value, wmap_aset_update, key); - st_insert(w->wmap2obj, (st_data_t)key, (st_data_t)value); - return nonspecial_obj_id(value); -} - -/* Retrieves a weakly referenced object with the given key */ -static VALUE -wmap_lookup(VALUE self, VALUE key) -{ - st_data_t data; - VALUE obj; - struct weakmap *w; - rb_objspace_t *objspace = &rb_objspace; - GC_ASSERT(wmap_live_p(objspace, key)); - - TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); - if (!st_lookup(w->wmap2obj, (st_data_t)key, &data)) return Qundef; - obj = (VALUE)data; - if (!wmap_live_p(objspace, obj)) return Qundef; - return obj; -} - -/* Retrieves a weakly referenced object with the given key */ -static VALUE -wmap_aref(VALUE self, VALUE key) -{ - VALUE obj = wmap_lookup(self, key); - return !UNDEF_P(obj) ? obj : Qnil; -} - -/* Returns +true+ if +key+ is registered */ -static VALUE -wmap_has_key(VALUE self, VALUE key) -{ - return RBOOL(!UNDEF_P(wmap_lookup(self, key))); -} - -/* Returns the number of referenced objects */ -static VALUE -wmap_size(VALUE self) -{ - struct weakmap *w; - st_index_t n; - - TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); - n = w->wmap2obj->num_entries; -#if SIZEOF_ST_INDEX_T <= SIZEOF_LONG - return ULONG2NUM(n); -#else - return ULL2NUM(n); -#endif -} - - -/* - ------------------------------ WeakKeyMap ------------------------------ -*/ - -typedef struct weakkeymap_entry { - VALUE obj; - st_index_t hash; -} weakkeymap_entry_t; - -struct weakkeymap { - st_table *map; - st_table *obj2hash; - VALUE final; -}; - -static int -weakkeymap_cmp_entry(st_data_t a, st_data_t b) -{ - struct weakkeymap_entry *entry_a = (struct weakkeymap_entry *)a; - struct weakkeymap_entry *entry_b = (struct weakkeymap_entry *)b; - if (entry_a == entry_b) { - return 0; - } - else { - return rb_any_cmp(entry_a->obj, entry_b->obj); - } -} - -static st_index_t -weakkeymap_hash_entry(st_data_t a) -{ - struct weakkeymap_entry *entry_a = (struct weakkeymap_entry *)a; - return entry_a->hash; -} - -static const struct st_hash_type weakkeymap_hash = { - weakkeymap_cmp_entry, - weakkeymap_hash_entry, -}; - -static void -wkmap_compact(void *ptr) -{ - struct weakkeymap *w = ptr; - if (w->map) rb_gc_update_tbl_refs(w->map); - w->final = rb_gc_location(w->final); -} - -static void -wkmap_mark(void *ptr) -{ - struct weakkeymap *w = ptr; - rb_mark_tbl_no_pin(w->map); - rb_gc_mark_movable(w->final); -} - -static void -wkmap_free(void *ptr) -{ - struct weakkeymap *w = ptr; - st_free_table(w->map); - st_free_table(w->obj2hash); - xfree(w); -} - -static size_t -wkmap_memsize(const void *ptr) -{ - const struct weakkeymap *w = ptr; - return sizeof(struct weakkeymap) + st_memsize(w->map) + st_memsize(w->obj2hash); -} - -static const rb_data_type_t weakkeymap_type = { - "weakkeymap", - { - wkmap_mark, - wkmap_free, - wkmap_memsize, - wkmap_compact, - }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY -}; - -static VALUE -wkmap_finalize(RB_BLOCK_CALL_FUNC_ARGLIST(objid, self)) -{ - struct weakkeymap *w; - VALUE key; - - TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w); - - /* Get reference from object id. */ - if ((key = id2ref_obj_tbl(&rb_objspace, objid)) == Qundef) { - rb_bug("wkmap_finalize: objid is not found."); - } - - st_index_t hash; - if (st_delete(w->obj2hash, (st_data_t *)key, &hash)) { - weakkeymap_entry_t lookup_entry = {key, hash}; - weakkeymap_entry_t *deleted_entry = NULL; - if (st_get_key(w->map, (st_data_t)&lookup_entry, (st_data_t *)deleted_entry)) { - st_data_t deleted_value; - st_delete(w->map, (st_data_t *)deleted_entry, &deleted_value); - xfree(deleted_entry); - } - } - - return self; -} - -static VALUE -wkmap_allocate(VALUE klass) -{ - struct weakkeymap *w; - VALUE obj = TypedData_Make_Struct(klass, struct weakkeymap, &weakkeymap_type, w); - w->map = st_init_table(&weakkeymap_hash); - w->obj2hash = rb_init_identtable(); - w->final = rb_func_lambda_new(wkmap_finalize, obj, 1, 1); - return obj; -} - -static st_index_t -wkmap_lookup_hash(struct weakkeymap *w, VALUE key) -{ - st_index_t hash; - if (!st_lookup(w->obj2hash, (st_data_t)key, &hash)) { - hash = rb_any_hash(key); - } - return hash; -} - -static weakkeymap_entry_t* -wkmap_lookup_entry(struct weakkeymap *w, VALUE key, st_index_t hash) -{ - st_data_t data; - weakkeymap_entry_t lookup_entry = {key, hash}; - - if (st_get_key(w->map, (st_data_t)&lookup_entry, &data)) { - return (weakkeymap_entry_t *)data; - } - - return NULL; -} - -static VALUE -wkmap_lookup(VALUE self, VALUE key) -{ - st_data_t data; - struct weakkeymap *w; - TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w); - - st_index_t hash = rb_any_hash(key); - weakkeymap_entry_t lookup_entry = {key, hash}; - - if (st_lookup(w->map, (st_data_t)&lookup_entry, &data)) { - return (VALUE)data; - } - return Qundef; -} - -/* - * call-seq: - * map[key] -> value - * - * Returns the value associated with the given +key+ if found. - * - * If +key+ is not found, returns +nil+. - */ -static VALUE -wkmap_aref(VALUE self, VALUE key) -{ - VALUE obj = wkmap_lookup(self, key); - return obj != Qundef ? obj : Qnil; -} - -/* - * call-seq: - * map[key] = value -> value - * - * Associates the given +value+ with the given +key+; returns +value+. - * - * The reference to +key+ is weak, so when there is no other reference - * to +key+ it may be garbage collected. - * - * If the given +key+ exists, replaces its value with the given +value+; - * the ordering is not affected - */ -static VALUE -wkmap_aset(VALUE self, VALUE key, VALUE value) -{ - struct weakkeymap *w; - TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w); - - if (!(FL_ABLE(key) && !SYMBOL_P(key) && !RB_BIGNUM_TYPE_P(key))) { - rb_raise(rb_eArgError, "WeakKeyMap must be garbage collectable"); - } - - st_index_t hash = wkmap_lookup_hash(w, key); - weakkeymap_entry_t *key_entry = wkmap_lookup_entry(w, key, hash); - - if (!key_entry) { - key_entry = ALLOC(weakkeymap_entry_t); - key_entry->obj = key; - key_entry->hash = hash; - } - - if (!st_insert(w->map, (st_data_t)key_entry, (st_data_t)value)) { - st_insert(w->obj2hash, (st_data_t)key, (st_data_t)hash); - define_final0(key, w->final); - } - - return value; -} - -/* - * call-seq: - * map.getkey(key) -> existing_key or nil - * - * Returns the existing equal key if it exists, otherwise returns +nil+. - */ -static VALUE -wkmap_getkey(VALUE self, VALUE key) -{ - struct weakkeymap *w; - TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w); - - st_index_t hash = rb_any_hash(key); - weakkeymap_entry_t lookup_entry = {key, hash}; - - weakkeymap_entry_t *key_entry = NULL; - if (st_get_key(w->map, (st_data_t)&lookup_entry, (st_data_t *)&key_entry)) { - GC_ASSERT(key_entry != NULL); - - VALUE obj = key_entry->obj; - if (wmap_live_p(&rb_objspace, obj)) { - return obj; - } - } - return Qnil; -} - -/* - * call-seq: - * hash.key?(key) -> true or false - * - * Returns +true+ if +key+ is a key in +self+, otherwise +false+. - */ -static VALUE -wkmap_has_key(VALUE self, VALUE key) -{ - return RBOOL(wkmap_lookup(self, key) != Qundef); -} - -/* - * call-seq: - * map.clear -> self - * - * Removes all map entries; returns +self+. - */ -static VALUE -wkmap_clear(VALUE self) -{ - struct weakkeymap *w; - TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w); - if (w->map) { - st_clear(w->map); - } - if (w->obj2hash) { - st_clear(w->obj2hash); - } - return self; -} - -/* - * call-seq: - * map.inspect -> new_string - * - * Returns a new \String containing informations about the map: - - * m = ObjectSpace::WeakKeyMap.new - * m[key] = value - * m.inspect # => "#" - * - */ -static VALUE -wkmap_inspect(VALUE self) -{ - struct weakkeymap *w; - TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w); - - st_index_t n = 0; - if (w->map) { - n = w->map->num_entries; - } - -#if SIZEOF_ST_INDEX_T <= SIZEOF_LONG - const char * format = "#<%"PRIsVALUE":%p size=%lu>"; -#else - const char * format = "#<%"PRIsVALUE":%p size=%llu>"; -#endif - - VALUE str = rb_sprintf(format, rb_class_name(CLASS_OF(self)), (void *)self, n); - return str; -} - /* ------------------------------ GC profiler ------------------------------ */ @@ -14817,25 +14046,6 @@ rb_gcdebug_remove_stress_to_class(int argc, VALUE *argv, VALUE self) * Finalizer one on 537763480 */ -/* - * Document-class: ObjectSpace::WeakMap - * - * An ObjectSpace::WeakMap object holds references to - * any objects, but those objects can get garbage collected. - * - * This class is mostly used internally by WeakRef, please use - * +lib/weakref.rb+ for the public interface. - */ - -/* - * Document-class: ObjectSpace::WeakKeyMap - * - * An ObjectSpace::WeakKeyMap object holds references to - * any objects, but objects uses as keys can be garbage collected. - * - * Objects used as values can't be garbage collected until the key is. - */ - /* Document-class: GC::Profiler * * The GC profiler provides access to information on GC runs including time, @@ -14925,37 +14135,6 @@ Init_GC(void) rb_define_module_function(rb_mObjSpace, "count_objects", count_objects, -1); - { - VALUE rb_cWeakMap = rb_define_class_under(rb_mObjSpace, "WeakMap", rb_cObject); - rb_define_alloc_func(rb_cWeakMap, wmap_allocate); - rb_define_method(rb_cWeakMap, "[]=", wmap_aset, 2); - rb_define_method(rb_cWeakMap, "[]", wmap_aref, 1); - rb_define_method(rb_cWeakMap, "include?", wmap_has_key, 1); - rb_define_method(rb_cWeakMap, "member?", wmap_has_key, 1); - rb_define_method(rb_cWeakMap, "key?", wmap_has_key, 1); - rb_define_method(rb_cWeakMap, "inspect", wmap_inspect, 0); - rb_define_method(rb_cWeakMap, "each", wmap_each, 0); - rb_define_method(rb_cWeakMap, "each_pair", wmap_each, 0); - rb_define_method(rb_cWeakMap, "each_key", wmap_each_key, 0); - rb_define_method(rb_cWeakMap, "each_value", wmap_each_value, 0); - rb_define_method(rb_cWeakMap, "keys", wmap_keys, 0); - rb_define_method(rb_cWeakMap, "values", wmap_values, 0); - rb_define_method(rb_cWeakMap, "size", wmap_size, 0); - rb_define_method(rb_cWeakMap, "length", wmap_size, 0); - rb_include_module(rb_cWeakMap, rb_mEnumerable); - } - - { - VALUE rb_cWeakKeyMap = rb_define_class_under(rb_mObjSpace, "WeakKeyMap", rb_cObject); - rb_define_alloc_func(rb_cWeakKeyMap, wkmap_allocate); - rb_define_method(rb_cWeakKeyMap, "[]=", wkmap_aset, 2); - rb_define_method(rb_cWeakKeyMap, "[]", wkmap_aref, 1); - rb_define_method(rb_cWeakKeyMap, "getkey", wkmap_getkey, 1); - rb_define_method(rb_cWeakKeyMap, "key?", wkmap_has_key, 1); - rb_define_method(rb_cWeakKeyMap, "clear", wkmap_clear, 0); - rb_define_method(rb_cWeakKeyMap, "inspect", wkmap_inspect, 0); - } - /* internal methods */ rb_define_singleton_method(rb_mGC, "verify_internal_consistency", gc_verify_internal_consistency_m, 0); rb_define_singleton_method(rb_mGC, "verify_transient_heap_internal_consistency", gc_verify_transient_heap_internal_consistency, 0); diff --git a/inits.c b/inits.c index f41103ca26..8b66334a4d 100644 --- a/inits.c +++ b/inits.c @@ -62,6 +62,7 @@ rb_call_inits(void) CALL(Binding); CALL(Math); CALL(GC); + CALL(WeakMap); CALL(Enumerator); CALL(Ractor); CALL(VM); diff --git a/internal/gc.h b/internal/gc.h index ad0ceac7de..73b6e6fed0 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -243,6 +243,9 @@ void rb_gc_ractor_newobj_cache_clear(rb_ractor_newobj_cache_t *newobj_cache); size_t rb_gc_obj_slot_size(VALUE obj); bool rb_gc_size_allocatable_p(size_t size); int rb_objspace_garbage_object_p(VALUE obj); +bool rb_gc_is_ptr_to_obj(void *ptr); +VALUE rb_gc_id2ref_obj_tbl(VALUE objid); +VALUE rb_define_finalizer_no_check(VALUE obj, VALUE block); void rb_gc_mark_and_move(VALUE *ptr); diff --git a/weakmap.c b/weakmap.c new file mode 100644 index 0000000000..6915b3c71d --- /dev/null +++ b/weakmap.c @@ -0,0 +1,800 @@ +#include "internal.h" +#include "internal/gc.h" +#include "internal/hash.h" +#include "internal/proc.h" +#include "internal/sanitizers.h" +#include "ruby/st.h" +#include "ruby/st.h" + +struct weakmap { + st_table *obj2wmap; /* obj -> [ref,...] */ + st_table *wmap2obj; /* ref -> obj */ + VALUE final; +}; + +static void +wmap_compact(void *ptr) +{ + struct weakmap *w = ptr; + if (w->wmap2obj) rb_gc_update_tbl_refs(w->wmap2obj); + if (w->obj2wmap) rb_gc_update_tbl_refs(w->obj2wmap); + w->final = rb_gc_location(w->final); +} + +static void +wmap_mark(void *ptr) +{ + struct weakmap *w = ptr; + rb_gc_mark_movable(w->final); +} + +static int +wmap_free_map(st_data_t key, st_data_t val, st_data_t arg) +{ + VALUE *ptr = (VALUE *)val; + ruby_sized_xfree(ptr, (ptr[0] + 1) * sizeof(VALUE)); + return ST_CONTINUE; +} + +static void +wmap_free(void *ptr) +{ + struct weakmap *w = ptr; + st_foreach(w->obj2wmap, wmap_free_map, 0); + st_free_table(w->obj2wmap); + st_free_table(w->wmap2obj); + xfree(w); +} + +static int +wmap_memsize_map(st_data_t key, st_data_t val, st_data_t arg) +{ + VALUE *ptr = (VALUE *)val; + *(size_t *)arg += (ptr[0] + 1) * sizeof(VALUE); + return ST_CONTINUE; +} + +static size_t +wmap_memsize(const void *ptr) +{ + size_t size; + const struct weakmap *w = ptr; + size = sizeof(*w); + size += st_memsize(w->obj2wmap); + size += st_memsize(w->wmap2obj); + st_foreach(w->obj2wmap, wmap_memsize_map, (st_data_t)&size); + return size; +} + +static const rb_data_type_t weakmap_type = { + "weakmap", + { + wmap_mark, + wmap_free, + wmap_memsize, + wmap_compact, + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY +}; + +static VALUE wmap_finalize(RB_BLOCK_CALL_FUNC_ARGLIST(objid, self)); + +static VALUE +wmap_allocate(VALUE klass) +{ + struct weakmap *w; + VALUE obj = TypedData_Make_Struct(klass, struct weakmap, &weakmap_type, w); + w->obj2wmap = rb_init_identtable(); + w->wmap2obj = rb_init_identtable(); + w->final = rb_func_lambda_new(wmap_finalize, obj, 1, 1); + return obj; +} + +static int +wmap_live_p(VALUE obj) +{ + if (SPECIAL_CONST_P(obj)) return TRUE; + /* If rb_gc_is_ptr_to_obj returns false, the page could be in the tomb heap + * or have already been freed. */ + if (!rb_gc_is_ptr_to_obj((void *)obj)) return FALSE; + + void *poisoned = asan_poisoned_object_p(obj); + asan_unpoison_object(obj, false); + + enum ruby_value_type t = BUILTIN_TYPE(obj); + int ret = (!(t == T_NONE || t >= T_FIXNUM || t == T_ICLASS) && + !rb_objspace_garbage_object_p(obj)); + + if (poisoned) { + asan_poison_object(obj); + } + + return ret; +} + +static int +wmap_final_func(st_data_t *key, st_data_t *value, st_data_t arg, int existing) +{ + VALUE wmap, *ptr, size, i, j; + if (!existing) return ST_STOP; + wmap = (VALUE)arg, ptr = (VALUE *)*value; + for (i = j = 1, size = ptr[0]; i <= size; ++i) { + if (ptr[i] != wmap) { + ptr[j++] = ptr[i]; + } + } + if (j == 1) { + ruby_sized_xfree(ptr, i * sizeof(VALUE)); + return ST_DELETE; + } + if (j < i) { + SIZED_REALLOC_N(ptr, VALUE, j + 1, i); + ptr[0] = j; + *value = (st_data_t)ptr; + } + return ST_CONTINUE; +} + +/* :nodoc: */ +static VALUE +wmap_finalize(RB_BLOCK_CALL_FUNC_ARGLIST(objid, self)) +{ + st_data_t orig, wmap, data; + VALUE obj, *rids, i, size; + struct weakmap *w; + + TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); + /* Get reference from object id. */ + if (UNDEF_P(obj = rb_gc_id2ref_obj_tbl(objid))) { + rb_bug("wmap_finalize: objid is not found."); + } + + /* obj is original referenced object and/or weak reference. */ + orig = (st_data_t)obj; + if (st_delete(w->obj2wmap, &orig, &data)) { + rids = (VALUE *)data; + size = *rids++; + for (i = 0; i < size; ++i) { + wmap = (st_data_t)rids[i]; + st_delete(w->wmap2obj, &wmap, NULL); + } + ruby_sized_xfree((VALUE *)data, (size + 1) * sizeof(VALUE)); + } + + wmap = (st_data_t)obj; + if (st_delete(w->wmap2obj, &wmap, &orig)) { + wmap = (st_data_t)obj; + st_update(w->obj2wmap, orig, wmap_final_func, wmap); + } + return self; +} + +static VALUE +wmap_inspect_append(VALUE str, VALUE obj) +{ + if (SPECIAL_CONST_P(obj)) { + return rb_str_append(str, rb_inspect(obj)); + } + else if (wmap_live_p(obj)) { + return rb_str_append(str, rb_any_to_s(obj)); + } + else { + return rb_str_catf(str, "#", (void*)obj); + } +} + +static int +wmap_inspect_i(st_data_t key, st_data_t val, st_data_t arg) +{ + VALUE str = (VALUE)arg; + VALUE k = (VALUE)key, v = (VALUE)val; + + if (RSTRING_PTR(str)[0] == '#') { + rb_str_cat2(str, ", "); + } + else { + rb_str_cat2(str, ": "); + RSTRING_PTR(str)[0] = '#'; + } + wmap_inspect_append(str, k); + rb_str_cat2(str, " => "); + wmap_inspect_append(str, v); + + return ST_CONTINUE; +} + +static VALUE +wmap_inspect(VALUE self) +{ + VALUE c = rb_class_name(CLASS_OF(self)); + struct weakmap *w; + TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); + + VALUE str = rb_sprintf("-<%"PRIsVALUE":%p", c, (void *)self); + if (w->wmap2obj) { + st_foreach(w->wmap2obj, wmap_inspect_i, (st_data_t)str); + } + RSTRING_PTR(str)[0] = '#'; + rb_str_cat2(str, ">"); + return str; +} + +static inline bool +wmap_live_entry_p(st_data_t key, st_data_t val) +{ + return wmap_live_p((VALUE)key) && wmap_live_p((VALUE)val); +} + +static int +wmap_each_i(st_data_t key, st_data_t val, st_data_t _) +{ + if (wmap_live_entry_p(key, val)) { + rb_yield_values(2, (VALUE)key, (VALUE)val); + return ST_CONTINUE; + } + else { + return ST_DELETE; + } +} + +/* Iterates over keys and objects in a weakly referenced object */ +static VALUE +wmap_each(VALUE self) +{ + struct weakmap *w; + + TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); + st_foreach(w->wmap2obj, wmap_each_i, (st_data_t)0); + return self; +} + +static int +wmap_each_key_i(st_data_t key, st_data_t val, st_data_t arg) +{ + if (wmap_live_entry_p(key, val)) { + rb_yield((VALUE)key); + return ST_CONTINUE; + } + else { + return ST_DELETE; + } +} + +/* Iterates over keys and objects in a weakly referenced object */ +static VALUE +wmap_each_key(VALUE self) +{ + struct weakmap *w; + + TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); + st_foreach(w->wmap2obj, wmap_each_key_i, (st_data_t)0); + return self; +} + +static int +wmap_each_value_i(st_data_t key, st_data_t val, st_data_t arg) +{ + if (wmap_live_entry_p(key, val)) { + rb_yield((VALUE)val); + return ST_CONTINUE; + } + else { + return ST_DELETE; + } +} + +/* Iterates over keys and objects in a weakly referenced object */ +static VALUE +wmap_each_value(VALUE self) +{ + struct weakmap *w; + + TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); + st_foreach(w->wmap2obj, wmap_each_value_i, (st_data_t)0); + return self; +} + +static int +wmap_keys_i(st_data_t key, st_data_t val, st_data_t arg) +{ + VALUE ary = (VALUE)arg; + + if (wmap_live_entry_p(key, val)) { + rb_ary_push(ary, (VALUE)key); + return ST_CONTINUE; + } + else { + return ST_DELETE; + } +} + +/* Iterates over keys and objects in a weakly referenced object */ +static VALUE +wmap_keys(VALUE self) +{ + struct weakmap *w; + TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); + + VALUE ary = rb_ary_new(); + st_foreach(w->wmap2obj, wmap_keys_i, (st_data_t)ary); + return ary; +} + +static int +wmap_values_i(st_data_t key, st_data_t val, st_data_t arg) +{ + VALUE ary = (VALUE)arg; + + if (wmap_live_entry_p(key, val)) { + rb_ary_push(ary, (VALUE)val); + return ST_CONTINUE; + } + else { + return ST_DELETE; + } +} + +/* Iterates over values and objects in a weakly referenced object */ +static VALUE +wmap_values(VALUE self) +{ + struct weakmap *w; + TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); + + VALUE ary = rb_ary_new(); + st_foreach(w->wmap2obj, wmap_values_i, (st_data_t)ary); + return ary; +} + +static int +wmap_aset_update(st_data_t *key, st_data_t *val, st_data_t arg, int existing) +{ + VALUE size, *ptr, *optr; + if (existing) { + size = (ptr = optr = (VALUE *)*val)[0]; + ++size; + SIZED_REALLOC_N(ptr, VALUE, size + 1, size); + } + else { + optr = 0; + size = 1; + ptr = ruby_xmalloc(2 * sizeof(VALUE)); + } + ptr[0] = size; + ptr[size] = (VALUE)arg; + if (ptr == optr) return ST_STOP; + *val = (st_data_t)ptr; + return ST_CONTINUE; +} + +static VALUE +nonspecial_obj_id(VALUE obj) +{ +#if SIZEOF_LONG == SIZEOF_VOIDP + return (VALUE)((SIGNED_VALUE)(obj)|FIXNUM_FLAG); +#elif SIZEOF_LONG_LONG == SIZEOF_VOIDP + return LL2NUM((SIGNED_VALUE)(obj) / 2); +#else +# error not supported +#endif +} + +/* Creates a weak reference from the given key to the given value */ +static VALUE +wmap_aset(VALUE self, VALUE key, VALUE value) +{ + struct weakmap *w; + + TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); + if (FL_ABLE(value)) { + rb_define_finalizer_no_check(value, w->final); + } + if (FL_ABLE(key)) { + rb_define_finalizer_no_check(key, w->final); + } + + st_update(w->obj2wmap, (st_data_t)value, wmap_aset_update, key); + st_insert(w->wmap2obj, (st_data_t)key, (st_data_t)value); + return nonspecial_obj_id(value); +} + +/* Retrieves a weakly referenced object with the given key */ +static VALUE +wmap_lookup(VALUE self, VALUE key) +{ + assert(wmap_live_p(key)); + + struct weakmap *w; + TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); + + st_data_t data; + if (!st_lookup(w->wmap2obj, (st_data_t)key, &data)) return Qundef; + + VALUE obj = (VALUE)data; + if (!wmap_live_p(obj)) return Qundef; + return obj; +} + +/* Retrieves a weakly referenced object with the given key */ +static VALUE +wmap_aref(VALUE self, VALUE key) +{ + VALUE obj = wmap_lookup(self, key); + return !UNDEF_P(obj) ? obj : Qnil; +} + +/* Returns +true+ if +key+ is registered */ +static VALUE +wmap_has_key(VALUE self, VALUE key) +{ + return RBOOL(!UNDEF_P(wmap_lookup(self, key))); +} + +/* Returns the number of referenced objects */ +static VALUE +wmap_size(VALUE self) +{ + struct weakmap *w; + st_index_t n; + + TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w); + n = w->wmap2obj->num_entries; +#if SIZEOF_ST_INDEX_T <= SIZEOF_LONG + return ULONG2NUM(n); +#else + return ULL2NUM(n); +#endif +} + +typedef struct weakkeymap_entry { + VALUE obj; + st_index_t hash; +} weakkeymap_entry_t; + +struct weakkeymap { + st_table *map; + st_table *obj2hash; + VALUE final; +}; + +static int +weakkeymap_cmp_entry(st_data_t a, st_data_t b) +{ + struct weakkeymap_entry *entry_a = (struct weakkeymap_entry *)a; + struct weakkeymap_entry *entry_b = (struct weakkeymap_entry *)b; + if (entry_a == entry_b) { + return 0; + } + else { + return rb_any_cmp(entry_a->obj, entry_b->obj); + } +} + +static st_index_t +weakkeymap_hash_entry(st_data_t a) +{ + struct weakkeymap_entry *entry_a = (struct weakkeymap_entry *)a; + return entry_a->hash; +} + +static const struct st_hash_type weakkeymap_hash = { + weakkeymap_cmp_entry, + weakkeymap_hash_entry, +}; + +static void +wkmap_compact(void *ptr) +{ + struct weakkeymap *w = ptr; + if (w->map) rb_gc_update_tbl_refs(w->map); + w->final = rb_gc_location(w->final); +} + +static void +wkmap_mark(void *ptr) +{ + struct weakkeymap *w = ptr; + rb_mark_tbl_no_pin(w->map); + rb_gc_mark_movable(w->final); +} + +static void +wkmap_free(void *ptr) +{ + struct weakkeymap *w = ptr; + st_free_table(w->map); + st_free_table(w->obj2hash); + xfree(w); +} + +static size_t +wkmap_memsize(const void *ptr) +{ + const struct weakkeymap *w = ptr; + return sizeof(struct weakkeymap) + st_memsize(w->map) + st_memsize(w->obj2hash); +} + +static const rb_data_type_t weakkeymap_type = { + "weakkeymap", + { + wkmap_mark, + wkmap_free, + wkmap_memsize, + wkmap_compact, + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY +}; + +static VALUE +wkmap_finalize(RB_BLOCK_CALL_FUNC_ARGLIST(objid, self)) +{ + struct weakkeymap *w; + VALUE key; + + TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w); + + /* Get reference from object id. */ + if ((key = rb_gc_id2ref_obj_tbl(objid)) == Qundef) { + rb_bug("wkmap_finalize: objid is not found."); + } + + st_index_t hash; + if (st_delete(w->obj2hash, (st_data_t *)key, &hash)) { + weakkeymap_entry_t lookup_entry = {key, hash}; + weakkeymap_entry_t *deleted_entry = NULL; + if (st_get_key(w->map, (st_data_t)&lookup_entry, (st_data_t *)deleted_entry)) { + st_data_t deleted_value; + st_delete(w->map, (st_data_t *)deleted_entry, &deleted_value); + xfree(deleted_entry); + } + } + + return self; +} + +static VALUE +wkmap_allocate(VALUE klass) +{ + struct weakkeymap *w; + VALUE obj = TypedData_Make_Struct(klass, struct weakkeymap, &weakkeymap_type, w); + w->map = st_init_table(&weakkeymap_hash); + w->obj2hash = rb_init_identtable(); + w->final = rb_func_lambda_new(wkmap_finalize, obj, 1, 1); + return obj; +} + +static st_index_t +wkmap_lookup_hash(struct weakkeymap *w, VALUE key) +{ + st_index_t hash; + if (!st_lookup(w->obj2hash, (st_data_t)key, &hash)) { + hash = rb_any_hash(key); + } + return hash; +} + +static weakkeymap_entry_t* +wkmap_lookup_entry(struct weakkeymap *w, VALUE key, st_index_t hash) +{ + st_data_t data; + weakkeymap_entry_t lookup_entry = {key, hash}; + + if (st_get_key(w->map, (st_data_t)&lookup_entry, &data)) { + return (weakkeymap_entry_t *)data; + } + + return NULL; +} + +static VALUE +wkmap_lookup(VALUE self, VALUE key) +{ + st_data_t data; + struct weakkeymap *w; + TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w); + + st_index_t hash = rb_any_hash(key); + weakkeymap_entry_t lookup_entry = {key, hash}; + + if (st_lookup(w->map, (st_data_t)&lookup_entry, &data)) { + return (VALUE)data; + } + return Qundef; +} + +/* + * call-seq: + * map[key] -> value + * + * Returns the value associated with the given +key+ if found. + * + * If +key+ is not found, returns +nil+. + */ +static VALUE +wkmap_aref(VALUE self, VALUE key) +{ + VALUE obj = wkmap_lookup(self, key); + return obj != Qundef ? obj : Qnil; +} + +/* + * call-seq: + * map[key] = value -> value + * + * Associates the given +value+ with the given +key+; returns +value+. + * + * The reference to +key+ is weak, so when there is no other reference + * to +key+ it may be garbage collected. + * + * If the given +key+ exists, replaces its value with the given +value+; + * the ordering is not affected + */ +static VALUE +wkmap_aset(VALUE self, VALUE key, VALUE value) +{ + struct weakkeymap *w; + TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w); + + if (!(FL_ABLE(key) && !SYMBOL_P(key) && !RB_BIGNUM_TYPE_P(key))) { + rb_raise(rb_eArgError, "WeakKeyMap must be garbage collectable"); + } + + st_index_t hash = wkmap_lookup_hash(w, key); + weakkeymap_entry_t *key_entry = wkmap_lookup_entry(w, key, hash); + + if (!key_entry) { + key_entry = ALLOC(weakkeymap_entry_t); + key_entry->obj = key; + key_entry->hash = hash; + } + + if (!st_insert(w->map, (st_data_t)key_entry, (st_data_t)value)) { + st_insert(w->obj2hash, (st_data_t)key, (st_data_t)hash); + rb_define_finalizer_no_check(key, w->final); + } + + return value; +} + +/* + * call-seq: + * map.getkey(key) -> existing_key or nil + * + * Returns the existing equal key if it exists, otherwise returns +nil+. + */ +static VALUE +wkmap_getkey(VALUE self, VALUE key) +{ + struct weakkeymap *w; + TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w); + + st_index_t hash = rb_any_hash(key); + weakkeymap_entry_t lookup_entry = {key, hash}; + + weakkeymap_entry_t *key_entry = NULL; + if (st_get_key(w->map, (st_data_t)&lookup_entry, (st_data_t *)&key_entry)) { + assert(key_entry != NULL); + + VALUE obj = key_entry->obj; + if (wmap_live_p(obj)) { + return obj; + } + } + return Qnil; +} + +/* + * call-seq: + * hash.key?(key) -> true or false + * + * Returns +true+ if +key+ is a key in +self+, otherwise +false+. + */ +static VALUE +wkmap_has_key(VALUE self, VALUE key) +{ + return RBOOL(wkmap_lookup(self, key) != Qundef); +} + +/* + * call-seq: + * map.clear -> self + * + * Removes all map entries; returns +self+. + */ +static VALUE +wkmap_clear(VALUE self) +{ + struct weakkeymap *w; + TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w); + if (w->map) { + st_clear(w->map); + } + if (w->obj2hash) { + st_clear(w->obj2hash); + } + return self; +} + +/* + * call-seq: + * map.inspect -> new_string + * + * Returns a new \String containing informations about the map: + + * m = ObjectSpace::WeakKeyMap.new + * m[key] = value + * m.inspect # => "#" + * + */ +static VALUE +wkmap_inspect(VALUE self) +{ + struct weakkeymap *w; + TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w); + + st_index_t n = 0; + if (w->map) { + n = w->map->num_entries; + } + +#if SIZEOF_ST_INDEX_T <= SIZEOF_LONG + const char * format = "#<%"PRIsVALUE":%p size=%lu>"; +#else + const char * format = "#<%"PRIsVALUE":%p size=%llu>"; +#endif + + VALUE str = rb_sprintf(format, rb_class_name(CLASS_OF(self)), (void *)self, n); + return str; +} + +/* + * Document-class: ObjectSpace::WeakMap + * + * An ObjectSpace::WeakMap object holds references to + * any objects, but those objects can get garbage collected. + * + * This class is mostly used internally by WeakRef, please use + * +lib/weakref.rb+ for the public interface. + */ + +/* + * Document-class: ObjectSpace::WeakKeyMap + * + * An ObjectSpace::WeakKeyMap object holds references to + * any objects, but objects uses as keys can be garbage collected. + * + * Objects used as values can't be garbage collected until the key is. + */ + +void +Init_WeakMap(void) +{ + VALUE rb_mObjectSpace = rb_define_module("ObjectSpace"); + + VALUE rb_cWeakMap = rb_define_class_under(rb_mObjectSpace, "WeakMap", rb_cObject); + rb_define_alloc_func(rb_cWeakMap, wmap_allocate); + rb_define_method(rb_cWeakMap, "[]=", wmap_aset, 2); + rb_define_method(rb_cWeakMap, "[]", wmap_aref, 1); + rb_define_method(rb_cWeakMap, "include?", wmap_has_key, 1); + rb_define_method(rb_cWeakMap, "member?", wmap_has_key, 1); + rb_define_method(rb_cWeakMap, "key?", wmap_has_key, 1); + rb_define_method(rb_cWeakMap, "inspect", wmap_inspect, 0); + rb_define_method(rb_cWeakMap, "each", wmap_each, 0); + rb_define_method(rb_cWeakMap, "each_pair", wmap_each, 0); + rb_define_method(rb_cWeakMap, "each_key", wmap_each_key, 0); + rb_define_method(rb_cWeakMap, "each_value", wmap_each_value, 0); + rb_define_method(rb_cWeakMap, "keys", wmap_keys, 0); + rb_define_method(rb_cWeakMap, "values", wmap_values, 0); + rb_define_method(rb_cWeakMap, "size", wmap_size, 0); + rb_define_method(rb_cWeakMap, "length", wmap_size, 0); + rb_include_module(rb_cWeakMap, rb_mEnumerable); + + VALUE rb_cWeakKeyMap = rb_define_class_under(rb_mObjectSpace, "WeakKeyMap", rb_cObject); + rb_define_alloc_func(rb_cWeakKeyMap, wkmap_allocate); + rb_define_method(rb_cWeakKeyMap, "[]=", wkmap_aset, 2); + rb_define_method(rb_cWeakKeyMap, "[]", wkmap_aref, 1); + rb_define_method(rb_cWeakKeyMap, "getkey", wkmap_getkey, 1); + rb_define_method(rb_cWeakKeyMap, "key?", wkmap_has_key, 1); + rb_define_method(rb_cWeakKeyMap, "clear", wkmap_clear, 0); + rb_define_method(rb_cWeakKeyMap, "inspect", wkmap_inspect, 0); +}