mirror of
https://github.com/ruby/ruby.git
synced 2025-08-15 13:39:04 +02:00
Extract Ractor safe table used for frozen strings
This commit extracts the Ractor safe table used for frozen strings into ractor_safe_table.c, which will allow it to be used elsewhere, including for the global symbol table.
This commit is contained in:
parent
e6cd79cd31
commit
d9b2d89976
5 changed files with 672 additions and 456 deletions
205
common.mk
205
common.mk
|
@ -151,6 +151,7 @@ COMMONOBJS = array.$(OBJEXT) \
|
|||
proc.$(OBJEXT) \
|
||||
process.$(OBJEXT) \
|
||||
ractor.$(OBJEXT) \
|
||||
ractor_safe_set.$(OBJEXT) \
|
||||
random.$(OBJEXT) \
|
||||
range.$(OBJEXT) \
|
||||
rational.$(OBJEXT) \
|
||||
|
@ -14301,6 +14302,209 @@ ractor.$(OBJEXT): {$(VPATH)}vm_debug.h
|
|||
ractor.$(OBJEXT): {$(VPATH)}vm_opts.h
|
||||
ractor.$(OBJEXT): {$(VPATH)}vm_sync.h
|
||||
ractor.$(OBJEXT): {$(VPATH)}yjit.h
|
||||
ractor_safe_set.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h
|
||||
ractor_safe_set.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
|
||||
ractor_safe_set.$(OBJEXT): $(CCAN_DIR)/list/list.h
|
||||
ractor_safe_set.$(OBJEXT): $(CCAN_DIR)/str/str.h
|
||||
ractor_safe_set.$(OBJEXT): $(hdrdir)/ruby/ruby.h
|
||||
ractor_safe_set.$(OBJEXT): $(top_srcdir)/internal/array.h
|
||||
ractor_safe_set.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
|
||||
ractor_safe_set.$(OBJEXT): $(top_srcdir)/internal/compilers.h
|
||||
ractor_safe_set.$(OBJEXT): $(top_srcdir)/internal/gc.h
|
||||
ractor_safe_set.$(OBJEXT): $(top_srcdir)/internal/imemo.h
|
||||
ractor_safe_set.$(OBJEXT): $(top_srcdir)/internal/namespace.h
|
||||
ractor_safe_set.$(OBJEXT): $(top_srcdir)/internal/ractor_safe_set.h
|
||||
ractor_safe_set.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h
|
||||
ractor_safe_set.$(OBJEXT): $(top_srcdir)/internal/serial.h
|
||||
ractor_safe_set.$(OBJEXT): $(top_srcdir)/internal/set_table.h
|
||||
ractor_safe_set.$(OBJEXT): $(top_srcdir)/internal/static_assert.h
|
||||
ractor_safe_set.$(OBJEXT): $(top_srcdir)/internal/vm.h
|
||||
ractor_safe_set.$(OBJEXT): $(top_srcdir)/internal/warnings.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}assert.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}atomic.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}backward/2/assume.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}backward/2/attributes.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}backward/2/bool.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}backward/2/gcc_version_since.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}backward/2/inttypes.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}backward/2/limits.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}backward/2/long_long.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}config.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}debug_counter.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}defines.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}encoding.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}id.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}id_table.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}intern.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/abi.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/anyargs.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/arithmetic.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/char.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/double.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/fixnum.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/gid_t.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/int.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/intptr_t.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/long.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/long_long.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/mode_t.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/off_t.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/pid_t.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/short.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/size_t.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/st_data_t.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/uid_t.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/assume.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/alloc_size.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/artificial.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/cold.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/const.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/constexpr.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/deprecated.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/diagnose_if.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/enum_extensibility.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/error.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/flag_enum.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/forceinline.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/format.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/maybe_unused.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/noalias.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/nodiscard.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/noexcept.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/noinline.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/nonnull.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/noreturn.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/packed_struct.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/pure.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/restrict.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/returns_nonnull.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/warning.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/attr/weakref.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/cast.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/compiler_is.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/compiler_is/apple.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/compiler_is/clang.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/compiler_is/gcc.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/compiler_is/intel.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/compiler_is/msvc.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/compiler_is/sunpro.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/compiler_since.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/config.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/constant_p.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/core.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/core/rarray.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/core/rbasic.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/core/rbignum.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/core/rclass.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/core/rdata.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/core/rfile.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/core/rhash.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/core/robject.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/core/rregexp.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/core/rstring.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/core/rstruct.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/core/rtypeddata.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/ctype.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/dllexport.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/dosish.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/encoding/coderange.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/encoding/ctype.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/encoding/encoding.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/encoding/pathname.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/encoding/re.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/encoding/sprintf.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/encoding/string.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/encoding/symbol.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/encoding/transcode.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/error.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/eval.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/event.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/fl_type.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/gc.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/glob.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/globals.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/has/attribute.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/has/builtin.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/has/c_attribute.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/has/cpp_attribute.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/has/declspec_attribute.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/has/extension.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/has/feature.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/has/warning.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/array.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/bignum.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/class.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/compar.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/complex.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/cont.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/dir.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/enum.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/enumerator.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/error.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/eval.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/file.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/hash.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/io.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/load.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/marshal.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/numeric.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/object.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/parse.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/proc.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/process.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/random.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/range.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/rational.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/re.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/select.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/signal.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/string.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/struct.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/thread.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/time.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/variable.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/intern/vm.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/interpreter.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/iterator.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/memory.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/method.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/module.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/newobj.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/scan_args.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/special_consts.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/static_assert.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/stdalign.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/stdbool.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/stdckdint.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/symbol.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/value.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/value_type.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/variable.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/warning_push.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}internal/xmalloc.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}method.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}missing.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}node.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}onigmo.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}oniguruma.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}ractor_safe_set.c
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}ruby_assert.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}ruby_atomic.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}rubyparser.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}st.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}subst.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}thread_native.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}vm_core.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}vm_debug.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}vm_opts.h
|
||||
ractor_safe_set.$(OBJEXT): {$(VPATH)}vm_sync.h
|
||||
random.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h
|
||||
random.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
|
||||
random.$(OBJEXT): $(CCAN_DIR)/list/list.h
|
||||
|
@ -18143,6 +18347,7 @@ string.$(OBJEXT): $(top_srcdir)/internal/namespace.h
|
|||
string.$(OBJEXT): $(top_srcdir)/internal/numeric.h
|
||||
string.$(OBJEXT): $(top_srcdir)/internal/object.h
|
||||
string.$(OBJEXT): $(top_srcdir)/internal/proc.h
|
||||
string.$(OBJEXT): $(top_srcdir)/internal/ractor_safe_set.h
|
||||
string.$(OBJEXT): $(top_srcdir)/internal/re.h
|
||||
string.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h
|
||||
string.$(OBJEXT): $(top_srcdir)/internal/serial.h
|
||||
|
|
21
gc.c
21
gc.c
|
@ -4005,16 +4005,24 @@ vm_weak_table_gen_fields_foreach(st_data_t key, st_data_t value, st_data_t data)
|
|||
}
|
||||
|
||||
static int
|
||||
vm_weak_table_frozen_strings_foreach(st_data_t key, st_data_t value, st_data_t data, int error)
|
||||
vm_weak_table_frozen_strings_foreach(VALUE *str, void *data)
|
||||
{
|
||||
int retval = vm_weak_table_foreach_weak_key(key, value, data, error);
|
||||
if (retval == ST_DELETE) {
|
||||
FL_UNSET((VALUE)key, RSTRING_FSTR);
|
||||
// int retval = vm_weak_table_foreach_weak_key(key, value, data, error);
|
||||
struct global_vm_table_foreach_data *iter_data = (struct global_vm_table_foreach_data *)data;
|
||||
int retval = iter_data->callback(*str, iter_data->data);
|
||||
|
||||
if (retval == ST_REPLACE) {
|
||||
retval = iter_data->update_callback(str, iter_data->data);
|
||||
}
|
||||
|
||||
if (retval == ST_DELETE) {
|
||||
FL_UNSET(*str, RSTRING_FSTR);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
void rb_fstring_foreach_with_replace(st_foreach_check_callback_func *func, st_update_callback_func *replace, st_data_t arg);
|
||||
void rb_fstring_foreach_with_replace(int (*callback)(VALUE *str, void *data), void *data);
|
||||
void
|
||||
rb_gc_vm_weak_table_foreach(vm_table_foreach_callback_func callback,
|
||||
vm_table_update_callback_func update_callback,
|
||||
|
@ -4090,8 +4098,7 @@ rb_gc_vm_weak_table_foreach(vm_table_foreach_callback_func callback,
|
|||
case RB_GC_VM_FROZEN_STRINGS_TABLE: {
|
||||
rb_fstring_foreach_with_replace(
|
||||
vm_weak_table_frozen_strings_foreach,
|
||||
vm_weak_table_foreach_update_weak_key,
|
||||
(st_data_t)&foreach_data
|
||||
&foreach_data
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
|
21
internal/ractor_safe_set.h
Normal file
21
internal/ractor_safe_set.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
#ifndef RUBY_RACTOR_SAFE_TABLE_H
|
||||
#define RUBY_RACTOR_SAFE_TABLE_H
|
||||
|
||||
#include "ruby/ruby.h"
|
||||
|
||||
typedef VALUE (*rb_ractor_safe_set_hash_func)(VALUE key);
|
||||
typedef bool (*rb_ractor_safe_set_cmp_func)(VALUE a, VALUE b);
|
||||
typedef VALUE (*rb_ractor_safe_set_create_func)(VALUE key, void *data);
|
||||
|
||||
struct rb_ractor_safe_set_funcs {
|
||||
rb_ractor_safe_set_hash_func hash;
|
||||
rb_ractor_safe_set_cmp_func cmp;
|
||||
rb_ractor_safe_set_create_func create;
|
||||
};
|
||||
|
||||
VALUE rb_ractor_safe_set_new(struct rb_ractor_safe_set_funcs *funcs, int capacity);
|
||||
VALUE rb_ractor_safe_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data);
|
||||
VALUE rb_ractor_safe_set_delete_by_identity(VALUE set_obj, VALUE key);
|
||||
void rb_ractor_safe_set_foreach_with_replace(VALUE set_obj, int (*callback)(VALUE *key, void *data), void *data);
|
||||
|
||||
#endif
|
325
ractor_safe_set.c
Normal file
325
ractor_safe_set.c
Normal file
|
@ -0,0 +1,325 @@
|
|||
#include "internal.h"
|
||||
#include "internal/gc.h"
|
||||
#include "internal/ractor_safe_set.h"
|
||||
#include "ruby_atomic.h"
|
||||
#include "ruby/atomic.h"
|
||||
#include "vm_sync.h"
|
||||
|
||||
enum ractor_safe_set_special_values {
|
||||
RACTOR_SAFE_TABLE_EMPTY,
|
||||
RACTOR_SAFE_TABLE_DELETED,
|
||||
RACTOR_SAFE_TABLE_MOVED,
|
||||
RACTOR_SAFE_TABLE_SPECIAL_VALUE_COUNT
|
||||
};
|
||||
|
||||
struct ractor_safe_set_entry {
|
||||
VALUE hash;
|
||||
VALUE key;
|
||||
};
|
||||
|
||||
struct ractor_safe_set {
|
||||
rb_atomic_t size;
|
||||
unsigned int capacity;
|
||||
unsigned int deleted_entries;
|
||||
struct rb_ractor_safe_set_funcs *funcs;
|
||||
struct ractor_safe_set_entry *entries;
|
||||
};
|
||||
|
||||
static void
|
||||
ractor_safe_set_free(void *ptr)
|
||||
{
|
||||
struct ractor_safe_set *set = ptr;
|
||||
xfree(set->entries);
|
||||
}
|
||||
|
||||
static size_t
|
||||
ractor_safe_set_size(const void *ptr)
|
||||
{
|
||||
const struct ractor_safe_set *set = ptr;
|
||||
return sizeof(struct ractor_safe_set) +
|
||||
(set->capacity * sizeof(struct ractor_safe_set_entry));
|
||||
}
|
||||
|
||||
static const rb_data_type_t ractor_safe_set_type = {
|
||||
.wrap_struct_name = "VM/ractor_safe_set",
|
||||
.function = {
|
||||
.dmark = NULL,
|
||||
.dfree = ractor_safe_set_free,
|
||||
.dsize = ractor_safe_set_size,
|
||||
},
|
||||
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE
|
||||
};
|
||||
|
||||
VALUE
|
||||
rb_ractor_safe_set_new(struct rb_ractor_safe_set_funcs *funcs, int capacity)
|
||||
{
|
||||
struct ractor_safe_set *set;
|
||||
VALUE obj = TypedData_Make_Struct(0, struct ractor_safe_set, &ractor_safe_set_type, set);
|
||||
set->funcs = funcs;
|
||||
set->entries = ZALLOC_N(struct ractor_safe_set_entry, capacity);
|
||||
set->capacity = capacity;
|
||||
return obj;
|
||||
}
|
||||
|
||||
struct ractor_safe_set_probe {
|
||||
int idx;
|
||||
int d;
|
||||
int mask;
|
||||
};
|
||||
|
||||
static int
|
||||
ractor_safe_set_probe_start(struct ractor_safe_set_probe *probe, struct ractor_safe_set *set, VALUE hash)
|
||||
{
|
||||
RUBY_ASSERT((set->capacity & (set->capacity - 1)) == 0);
|
||||
probe->d = 0;
|
||||
probe->mask = set->capacity - 1;
|
||||
probe->idx = hash & probe->mask;
|
||||
return probe->idx;
|
||||
}
|
||||
|
||||
static int
|
||||
ractor_safe_set_probe_next(struct ractor_safe_set_probe *probe)
|
||||
{
|
||||
probe->d++;
|
||||
probe->idx = (probe->idx + probe->d) & probe->mask;
|
||||
return probe->idx;
|
||||
}
|
||||
|
||||
static void
|
||||
ractor_safe_set_try_resize_without_locking(VALUE old_set_obj, VALUE *set_obj_ptr)
|
||||
{
|
||||
// Check if another thread has already resized.
|
||||
if (RUBY_ATOMIC_VALUE_LOAD(*set_obj_ptr) != old_set_obj) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct ractor_safe_set *old_set = RTYPEDDATA_GET_DATA(old_set_obj);
|
||||
|
||||
// This may overcount by up to the number of threads concurrently attempting to insert
|
||||
// GC may also happen between now and the set being rebuilt
|
||||
int expected_size = RUBY_ATOMIC_LOAD(old_set->size) - old_set->deleted_entries;
|
||||
|
||||
struct ractor_safe_set_entry *old_entries = old_set->entries;
|
||||
int old_capacity = old_set->capacity;
|
||||
int new_capacity = old_capacity * 2;
|
||||
if (new_capacity > expected_size * 8) {
|
||||
new_capacity = old_capacity / 2;
|
||||
}
|
||||
else if (new_capacity > expected_size * 4) {
|
||||
new_capacity = old_capacity;
|
||||
}
|
||||
|
||||
// May cause GC and therefore deletes, so must hapen first.
|
||||
VALUE new_set_obj = rb_ractor_safe_set_new(old_set->funcs, new_capacity);
|
||||
struct ractor_safe_set *new_set = RTYPEDDATA_GET_DATA(new_set_obj);
|
||||
|
||||
for (int i = 0; i < old_capacity; i++) {
|
||||
struct ractor_safe_set_entry *entry = &old_entries[i];
|
||||
VALUE key = RUBY_ATOMIC_VALUE_EXCHANGE(entry->key, RACTOR_SAFE_TABLE_MOVED);
|
||||
RUBY_ASSERT(key != RACTOR_SAFE_TABLE_MOVED);
|
||||
|
||||
if (key < RACTOR_SAFE_TABLE_SPECIAL_VALUE_COUNT) continue;
|
||||
if (rb_objspace_garbage_object_p(key)) continue;
|
||||
|
||||
VALUE hash = RUBY_ATOMIC_VALUE_LOAD(entry->hash);
|
||||
if (hash == 0) {
|
||||
// Either in-progress insert or extremely unlikely 0 hash.
|
||||
// Re-calculate the hash.
|
||||
hash = old_set->funcs->hash(key);
|
||||
}
|
||||
RUBY_ASSERT(hash == old_set->funcs->hash(key));
|
||||
|
||||
// Insert key into new_set.
|
||||
struct ractor_safe_set_probe probe;
|
||||
int idx = ractor_safe_set_probe_start(&probe, new_set, hash);
|
||||
|
||||
while (true) {
|
||||
struct ractor_safe_set_entry *entry = &new_set->entries[idx];
|
||||
|
||||
if (entry->key == RACTOR_SAFE_TABLE_EMPTY) {
|
||||
new_set->size++;
|
||||
|
||||
RUBY_ASSERT(new_set->size < new_set->capacity / 2);
|
||||
RUBY_ASSERT(entry->hash == 0);
|
||||
|
||||
entry->key = key;
|
||||
entry->hash = hash;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
RUBY_ASSERT(entry->key >= RACTOR_SAFE_TABLE_SPECIAL_VALUE_COUNT);
|
||||
}
|
||||
|
||||
idx = ractor_safe_set_probe_next(&probe);
|
||||
}
|
||||
}
|
||||
|
||||
RUBY_ATOMIC_VALUE_SET(*set_obj_ptr, new_set_obj);
|
||||
|
||||
RB_GC_GUARD(old_set_obj);
|
||||
}
|
||||
|
||||
static void
|
||||
ractor_safe_set_try_resize(VALUE old_set_obj, VALUE *set_obj_ptr)
|
||||
{
|
||||
RB_VM_LOCKING() {
|
||||
ractor_safe_set_try_resize_without_locking(old_set_obj, set_obj_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
VALUE
|
||||
rb_ractor_safe_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data)
|
||||
{
|
||||
RUBY_ASSERT(key >= RACTOR_SAFE_TABLE_SPECIAL_VALUE_COUNT);
|
||||
|
||||
bool inserting = false;
|
||||
VALUE set_obj;
|
||||
|
||||
retry:
|
||||
set_obj = RUBY_ATOMIC_VALUE_LOAD(*set_obj_ptr);
|
||||
RUBY_ASSERT(set_obj);
|
||||
struct ractor_safe_set *set = RTYPEDDATA_GET_DATA(set_obj);
|
||||
|
||||
struct ractor_safe_set_probe probe;
|
||||
VALUE hash = set->funcs->hash(key);
|
||||
int idx = ractor_safe_set_probe_start(&probe, set, hash);
|
||||
|
||||
while (true) {
|
||||
struct ractor_safe_set_entry *entry = &set->entries[idx];
|
||||
VALUE curr_key = RUBY_ATOMIC_VALUE_LOAD(entry->key);
|
||||
|
||||
switch (curr_key) {
|
||||
case RACTOR_SAFE_TABLE_EMPTY: {
|
||||
// Not in set
|
||||
if (!inserting) {
|
||||
key = set->funcs->create(key, data);
|
||||
RUBY_ASSERT(hash == set->funcs->hash(key));
|
||||
inserting = true;
|
||||
}
|
||||
|
||||
rb_atomic_t prev_size = RUBY_ATOMIC_FETCH_ADD(set->size, 1);
|
||||
|
||||
if (UNLIKELY(prev_size > set->capacity / 2)) {
|
||||
ractor_safe_set_try_resize(set_obj, set_obj_ptr);
|
||||
|
||||
goto retry;
|
||||
}
|
||||
|
||||
curr_key = RUBY_ATOMIC_VALUE_CAS(entry->key, RACTOR_SAFE_TABLE_EMPTY, key);
|
||||
if (curr_key == RACTOR_SAFE_TABLE_EMPTY) {
|
||||
RUBY_ATOMIC_VALUE_SET(entry->hash, hash);
|
||||
|
||||
RB_GC_GUARD(set_obj);
|
||||
return key;
|
||||
}
|
||||
else {
|
||||
// Entry was not inserted.
|
||||
RUBY_ATOMIC_DEC(set->size);
|
||||
|
||||
// Another thread won the race, try again at the same location.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
case RACTOR_SAFE_TABLE_DELETED:
|
||||
break;
|
||||
case RACTOR_SAFE_TABLE_MOVED:
|
||||
// Wait
|
||||
RB_VM_LOCKING();
|
||||
|
||||
goto retry;
|
||||
default: {
|
||||
VALUE curr_hash = RUBY_ATOMIC_VALUE_LOAD(entry->hash);
|
||||
if ((curr_hash == hash || curr_hash == 0) && set->funcs->cmp(key, curr_key)) {
|
||||
// We've found a match.
|
||||
if (UNLIKELY(rb_objspace_garbage_object_p(curr_key))) {
|
||||
// This is a weakref set, so after marking but before sweeping is complete we may find a matching garbage object.
|
||||
// Skip it and mark it as deleted.
|
||||
RUBY_ATOMIC_VALUE_CAS(entry->key, curr_key, RACTOR_SAFE_TABLE_DELETED);
|
||||
|
||||
// Fall through and continue our search.
|
||||
}
|
||||
else {
|
||||
RB_GC_GUARD(set_obj);
|
||||
return curr_key;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
idx = ractor_safe_set_probe_next(&probe);
|
||||
}
|
||||
}
|
||||
|
||||
VALUE
|
||||
rb_ractor_safe_set_delete_by_identity(VALUE set_obj, VALUE key)
|
||||
{
|
||||
// Assume locking and barrier (which there is no assert for).
|
||||
ASSERT_vm_locking();
|
||||
|
||||
struct ractor_safe_set *set = RTYPEDDATA_GET_DATA(set_obj);
|
||||
|
||||
VALUE hash = set->funcs->hash(key);
|
||||
|
||||
struct ractor_safe_set_probe probe;
|
||||
int idx = ractor_safe_set_probe_start(&probe, set, hash);
|
||||
|
||||
while (true) {
|
||||
struct ractor_safe_set_entry *entry = &set->entries[idx];
|
||||
VALUE curr_key = RUBY_ATOMIC_VALUE_LOAD(entry->key);
|
||||
|
||||
switch (curr_key) {
|
||||
case RACTOR_SAFE_TABLE_EMPTY:
|
||||
// We didn't find our entry to delete.
|
||||
return 0;
|
||||
case RACTOR_SAFE_TABLE_DELETED:
|
||||
break;
|
||||
case RACTOR_SAFE_TABLE_MOVED:
|
||||
rb_bug("rb_ractor_safe_set_delete_by_identity: moved entry");
|
||||
break;
|
||||
default:
|
||||
if (key == curr_key) {
|
||||
entry->key = RACTOR_SAFE_TABLE_DELETED;
|
||||
set->deleted_entries++;
|
||||
return curr_key;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
idx = ractor_safe_set_probe_next(&probe);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
rb_ractor_safe_set_foreach_with_replace(VALUE set_obj, int (*callback)(VALUE *key, void *data), void *data)
|
||||
{
|
||||
// Assume locking and barrier (which there is no assert for).
|
||||
ASSERT_vm_locking();
|
||||
|
||||
struct ractor_safe_set *set = RTYPEDDATA_GET_DATA(set_obj);
|
||||
|
||||
for (unsigned int i = 0; i < set->capacity; i++) {
|
||||
VALUE key = set->entries[i].key;
|
||||
|
||||
switch (key) {
|
||||
case RACTOR_SAFE_TABLE_EMPTY:
|
||||
case RACTOR_SAFE_TABLE_DELETED:
|
||||
continue;
|
||||
case RACTOR_SAFE_TABLE_MOVED:
|
||||
rb_bug("rb_ractor_safe_set_foreach_with_replace: moved entry");
|
||||
break;
|
||||
default: {
|
||||
int ret = callback(&set->entries[i].key, data);
|
||||
switch (ret) {
|
||||
case ST_STOP:
|
||||
return;
|
||||
case ST_DELETE:
|
||||
set->entries[i].key = RACTOR_SAFE_TABLE_DELETED;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
556
string.c
556
string.c
|
@ -35,6 +35,7 @@
|
|||
#include "internal/numeric.h"
|
||||
#include "internal/object.h"
|
||||
#include "internal/proc.h"
|
||||
#include "internal/ractor_safe_set.h"
|
||||
#include "internal/re.h"
|
||||
#include "internal/sanitizers.h"
|
||||
#include "internal/string.h"
|
||||
|
@ -356,8 +357,6 @@ mustnot_wchar(VALUE str)
|
|||
}
|
||||
}
|
||||
|
||||
static int fstring_cmp(VALUE a, VALUE b);
|
||||
|
||||
static VALUE register_fstring(VALUE str, bool copy, bool force_precompute_hash);
|
||||
|
||||
#if SIZEOF_LONG == SIZEOF_VOIDP
|
||||
|
@ -365,26 +364,6 @@ static VALUE register_fstring(VALUE str, bool copy, bool force_precompute_hash);
|
|||
#else
|
||||
#endif
|
||||
|
||||
#ifdef PRECOMPUTED_FAKESTR_HASH
|
||||
static st_index_t
|
||||
fstring_hash(VALUE str)
|
||||
{
|
||||
st_index_t h;
|
||||
if (FL_TEST_RAW(str, STR_FAKESTR)) {
|
||||
// register_fstring precomputes the hash and stores it in capa for fake strings
|
||||
h = (st_index_t)RSTRING(str)->as.heap.aux.capa;
|
||||
}
|
||||
else {
|
||||
h = rb_str_hash(str);
|
||||
}
|
||||
// rb_str_hash doesn't include the encoding for ascii only strings, so
|
||||
// we add it to avoid common collisions between `:sym.name` (ASCII) and `"sym"` (UTF-8)
|
||||
return rb_hash_end(rb_hash_uint32(h, (uint32_t)ENCODING_GET_INLINED(str)));
|
||||
}
|
||||
#else
|
||||
#define fstring_hash rb_str_hash
|
||||
#endif
|
||||
|
||||
static inline bool
|
||||
BARE_STRING_P(VALUE str)
|
||||
{
|
||||
|
@ -421,14 +400,91 @@ str_store_precomputed_hash(VALUE str, st_index_t hash)
|
|||
return str;
|
||||
}
|
||||
|
||||
struct fstr_update_arg {
|
||||
VALUE
|
||||
rb_fstring(VALUE str)
|
||||
{
|
||||
VALUE fstr;
|
||||
int bare;
|
||||
|
||||
Check_Type(str, T_STRING);
|
||||
|
||||
if (FL_TEST(str, RSTRING_FSTR))
|
||||
return str;
|
||||
|
||||
bare = BARE_STRING_P(str);
|
||||
if (!bare) {
|
||||
if (STR_EMBED_P(str)) {
|
||||
OBJ_FREEZE(str);
|
||||
return str;
|
||||
}
|
||||
|
||||
if (FL_TEST_RAW(str, STR_SHARED_ROOT | STR_SHARED) == STR_SHARED_ROOT) {
|
||||
RUBY_ASSERT(OBJ_FROZEN(str));
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
if (!FL_TEST_RAW(str, FL_FREEZE | STR_NOFREE | STR_CHILLED))
|
||||
rb_str_resize(str, RSTRING_LEN(str));
|
||||
|
||||
fstr = register_fstring(str, false, false);
|
||||
|
||||
if (!bare) {
|
||||
str_replace_shared_without_enc(str, fstr);
|
||||
OBJ_FREEZE(str);
|
||||
return str;
|
||||
}
|
||||
return fstr;
|
||||
}
|
||||
|
||||
static VALUE fstring_table_obj;
|
||||
|
||||
static VALUE
|
||||
fstring_ractor_safe_set_hash(VALUE str)
|
||||
{
|
||||
#ifdef PRECOMPUTED_FAKESTR_HASH
|
||||
st_index_t h;
|
||||
if (FL_TEST_RAW(str, STR_FAKESTR)) {
|
||||
// register_fstring precomputes the hash and stores it in capa for fake strings
|
||||
h = (st_index_t)RSTRING(str)->as.heap.aux.capa;
|
||||
}
|
||||
else {
|
||||
h = rb_str_hash(str);
|
||||
}
|
||||
// rb_str_hash doesn't include the encoding for ascii only strings, so
|
||||
// we add it to avoid common collisions between `:sym.name` (ASCII) and `"sym"` (UTF-8)
|
||||
return (VALUE)rb_hash_end(rb_hash_uint32(h, (uint32_t)ENCODING_GET_INLINED(str)));
|
||||
#else
|
||||
return (VALUE)rb_str_hash(str);
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool
|
||||
fstring_ractor_safe_set_cmp(VALUE a, VALUE b)
|
||||
{
|
||||
long alen, blen;
|
||||
const char *aptr, *bptr;
|
||||
|
||||
RUBY_ASSERT(RB_TYPE_P(a, T_STRING));
|
||||
RUBY_ASSERT(RB_TYPE_P(b, T_STRING));
|
||||
|
||||
RSTRING_GETMEM(a, aptr, alen);
|
||||
RSTRING_GETMEM(b, bptr, blen);
|
||||
return (alen == blen &&
|
||||
ENCODING_GET(a) == ENCODING_GET(b) &&
|
||||
memcmp(aptr, bptr, alen) == 0);
|
||||
}
|
||||
|
||||
struct fstr_create_arg {
|
||||
bool copy;
|
||||
bool force_precompute_hash;
|
||||
};
|
||||
|
||||
static VALUE
|
||||
build_fstring(VALUE str, struct fstr_update_arg *arg)
|
||||
fstring_ractor_safe_set_create(VALUE str, void *data)
|
||||
{
|
||||
struct fstr_create_arg *arg = data;
|
||||
|
||||
// Unless the string is empty or binary, its coderange has been precomputed.
|
||||
int coderange = ENC_CODERANGE(str);
|
||||
|
||||
|
@ -492,375 +548,23 @@ build_fstring(VALUE str, struct fstr_update_arg *arg)
|
|||
return str;
|
||||
}
|
||||
|
||||
VALUE
|
||||
rb_fstring(VALUE str)
|
||||
{
|
||||
VALUE fstr;
|
||||
int bare;
|
||||
|
||||
Check_Type(str, T_STRING);
|
||||
|
||||
if (FL_TEST(str, RSTRING_FSTR))
|
||||
return str;
|
||||
|
||||
bare = BARE_STRING_P(str);
|
||||
if (!bare) {
|
||||
if (STR_EMBED_P(str)) {
|
||||
OBJ_FREEZE(str);
|
||||
return str;
|
||||
}
|
||||
|
||||
if (FL_TEST_RAW(str, STR_SHARED_ROOT | STR_SHARED) == STR_SHARED_ROOT) {
|
||||
RUBY_ASSERT(OBJ_FROZEN(str));
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
if (!FL_TEST_RAW(str, FL_FREEZE | STR_NOFREE | STR_CHILLED))
|
||||
rb_str_resize(str, RSTRING_LEN(str));
|
||||
|
||||
fstr = register_fstring(str, false, false);
|
||||
|
||||
if (!bare) {
|
||||
str_replace_shared_without_enc(str, fstr);
|
||||
OBJ_FREEZE(str);
|
||||
return str;
|
||||
}
|
||||
return fstr;
|
||||
}
|
||||
|
||||
#define FSTRING_TABLE_EMPTY Qfalse
|
||||
#define FSTRING_TABLE_TOMBSTONE Qtrue
|
||||
#define FSTRING_TABLE_MOVED Qundef
|
||||
|
||||
struct fstring_table_entry {
|
||||
VALUE str;
|
||||
VALUE hash;
|
||||
static struct rb_ractor_safe_set_funcs fstring_ractor_safe_set_funcs = {
|
||||
.hash = fstring_ractor_safe_set_hash,
|
||||
.cmp = fstring_ractor_safe_set_cmp,
|
||||
.create = fstring_ractor_safe_set_create,
|
||||
};
|
||||
|
||||
struct fstring_table_struct {
|
||||
struct fstring_table_entry *entries;
|
||||
unsigned int capacity;
|
||||
unsigned int deleted_entries;
|
||||
rb_atomic_t count; // TODO: pad to own cache line?
|
||||
};
|
||||
|
||||
static void
|
||||
fstring_table_free(void *ptr)
|
||||
{
|
||||
struct fstring_table_struct *table = ptr;
|
||||
xfree(table->entries);
|
||||
}
|
||||
|
||||
static size_t
|
||||
fstring_table_size(const void *ptr)
|
||||
{
|
||||
const struct fstring_table_struct *table = ptr;
|
||||
return sizeof(struct fstring_table_struct) + sizeof(struct fstring_table_entry) * table->capacity;
|
||||
}
|
||||
|
||||
// We declare a type for the table so that we can lean on Ruby's GC for deferred reclamation
|
||||
static const rb_data_type_t fstring_table_type = {
|
||||
.wrap_struct_name = "VM/fstring_table",
|
||||
.function = {
|
||||
.dmark = NULL,
|
||||
.dfree = fstring_table_free,
|
||||
.dsize = fstring_table_size,
|
||||
},
|
||||
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE
|
||||
};
|
||||
|
||||
|
||||
static VALUE fstring_table_obj;
|
||||
|
||||
static VALUE
|
||||
new_fstring_table(int capacity)
|
||||
{
|
||||
VALUE obj;
|
||||
struct fstring_table_struct *table;
|
||||
obj = TypedData_Make_Struct(0, struct fstring_table_struct, &fstring_table_type, table);
|
||||
table->capacity = capacity;
|
||||
table->count = 0;
|
||||
table->entries = ZALLOC_N(struct fstring_table_entry, capacity);
|
||||
return obj;
|
||||
}
|
||||
|
||||
void
|
||||
Init_fstring_table(void)
|
||||
{
|
||||
fstring_table_obj = new_fstring_table(8192);
|
||||
fstring_table_obj = rb_ractor_safe_set_new(&fstring_ractor_safe_set_funcs, 8192);
|
||||
rb_gc_register_address(&fstring_table_obj);
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
// Linear probe
|
||||
struct fstring_table_probe {
|
||||
int idx;
|
||||
int mask;
|
||||
};
|
||||
|
||||
static int
|
||||
fstring_table_probe_start(struct fstring_table_probe *probe, struct fstring_table_struct *table, VALUE hash_code)
|
||||
{
|
||||
RUBY_ASSERT((table->capacity & (table->capacity - 1)) == 0);
|
||||
probe->mask = table->capacity - 1;
|
||||
probe->idx = hash_code & probe->mask;
|
||||
return probe->idx;
|
||||
}
|
||||
|
||||
static int
|
||||
fstring_table_probe_next(struct fstring_table_probe *probe)
|
||||
{
|
||||
probe->idx = (probe->idx + 1) & probe->mask;
|
||||
return probe->idx;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// Struct containing probe information. Intended that the compiler should always inline this
|
||||
// Quadratic probing
|
||||
struct fstring_table_probe {
|
||||
int idx;
|
||||
int d;
|
||||
int mask;
|
||||
};
|
||||
|
||||
static int
|
||||
fstring_table_probe_start(struct fstring_table_probe *probe, struct fstring_table_struct *table, VALUE hash_code)
|
||||
{
|
||||
RUBY_ASSERT((table->capacity & (table->capacity - 1)) == 0);
|
||||
probe->d = 0;
|
||||
probe->mask = table->capacity - 1;
|
||||
probe->idx = hash_code & probe->mask;
|
||||
return probe->idx;
|
||||
}
|
||||
|
||||
static int
|
||||
fstring_table_probe_next(struct fstring_table_probe *probe)
|
||||
{
|
||||
probe->d++;
|
||||
probe->idx = (probe->idx + probe->d) & probe->mask;
|
||||
return probe->idx;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
fstring_insert_on_resize(struct fstring_table_struct *table, VALUE hash_code, VALUE value)
|
||||
{
|
||||
struct fstring_table_probe probe;
|
||||
int idx = fstring_table_probe_start(&probe, table, hash_code);
|
||||
|
||||
for (;;) {
|
||||
struct fstring_table_entry *entry = &table->entries[idx];
|
||||
VALUE candidate = entry->str;
|
||||
|
||||
RUBY_ASSERT(candidate != FSTRING_TABLE_TOMBSTONE);
|
||||
RUBY_ASSERT(candidate != FSTRING_TABLE_MOVED);
|
||||
|
||||
if (candidate == FSTRING_TABLE_EMPTY) {
|
||||
table->count++;
|
||||
|
||||
RUBY_ASSERT(table->count < table->capacity / 2);
|
||||
RUBY_ASSERT(entry->hash == 0);
|
||||
|
||||
entry->str = value;
|
||||
entry->hash = hash_code;
|
||||
return;
|
||||
}
|
||||
|
||||
idx = fstring_table_probe_next(&probe);
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuilds the table
|
||||
static void
|
||||
fstring_try_resize_without_locking(VALUE old_table_obj)
|
||||
{
|
||||
// Check if another thread has already resized
|
||||
if (RUBY_ATOMIC_VALUE_LOAD(fstring_table_obj) != old_table_obj) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
struct fstring_table_struct *old_table = RTYPEDDATA_GET_DATA(old_table_obj);
|
||||
|
||||
// This may overcount by up to the number of threads concurrently attempting to insert
|
||||
// GC may also happen between now and the table being rebuilt
|
||||
int expected_count = RUBY_ATOMIC_LOAD(old_table->count) - old_table->deleted_entries;
|
||||
|
||||
struct fstring_table_entry *old_entries = old_table->entries;
|
||||
int old_capacity = old_table->capacity;
|
||||
int new_capacity = old_capacity * 2;
|
||||
if (new_capacity > expected_count * 8) {
|
||||
new_capacity = old_capacity / 2;
|
||||
}
|
||||
else if (new_capacity > expected_count * 4) {
|
||||
new_capacity = old_capacity;
|
||||
}
|
||||
|
||||
// May cause GC and therefore deletes, so must hapen first
|
||||
VALUE new_table_obj = new_fstring_table(new_capacity);
|
||||
struct fstring_table_struct *new_table = RTYPEDDATA_GET_DATA(new_table_obj);
|
||||
|
||||
for (int i = 0; i < old_capacity; i++) {
|
||||
struct fstring_table_entry *entry = &old_entries[i];
|
||||
VALUE val = RUBY_ATOMIC_VALUE_EXCHANGE(entry->str, FSTRING_TABLE_MOVED);
|
||||
RUBY_ASSERT(val != FSTRING_TABLE_MOVED);
|
||||
if (val == FSTRING_TABLE_EMPTY) continue;
|
||||
if (val == FSTRING_TABLE_TOMBSTONE) continue;
|
||||
if (rb_objspace_garbage_object_p(val)) continue;
|
||||
|
||||
VALUE hash_code = RUBY_ATOMIC_VALUE_LOAD(entry->hash);
|
||||
if (hash_code == 0) {
|
||||
// Either in-progress insert or extremely unlikely 0 hash
|
||||
// Re-calculate the hash ourselves
|
||||
hash_code = fstring_hash(val);
|
||||
}
|
||||
RUBY_ASSERT(hash_code == fstring_hash(val));
|
||||
fstring_insert_on_resize(new_table, hash_code, val);
|
||||
}
|
||||
|
||||
#if 0
|
||||
fprintf(stderr, "resized: %p(%i) -> %p(%i) (count: %i->%i)\n", old_table, old_table->capacity, new_table, new_table->capacity, old_table->count, new_table->count);
|
||||
#endif
|
||||
|
||||
RUBY_ATOMIC_VALUE_SET(fstring_table_obj, new_table_obj);
|
||||
|
||||
end:
|
||||
RB_GC_GUARD(old_table_obj);
|
||||
}
|
||||
|
||||
static void
|
||||
fstring_try_resize(VALUE old_table_obj)
|
||||
{
|
||||
RB_VM_LOCKING() {
|
||||
fstring_try_resize_without_locking(old_table_obj);
|
||||
}
|
||||
}
|
||||
|
||||
static VALUE
|
||||
fstring_find_or_insert(VALUE hash_code, VALUE value, struct fstr_update_arg *arg)
|
||||
{
|
||||
struct fstring_table_probe probe;
|
||||
bool inserting = false;
|
||||
int idx;
|
||||
VALUE table_obj;
|
||||
struct fstring_table_struct *table;
|
||||
|
||||
retry:
|
||||
table_obj = RUBY_ATOMIC_VALUE_LOAD(fstring_table_obj);
|
||||
RUBY_ASSERT(table_obj);
|
||||
table = RTYPEDDATA_GET_DATA(table_obj);
|
||||
idx = fstring_table_probe_start(&probe, table, hash_code);
|
||||
|
||||
for (;;) {
|
||||
struct fstring_table_entry *entry = &table->entries[idx];
|
||||
VALUE candidate = RUBY_ATOMIC_VALUE_LOAD(entry->str);
|
||||
|
||||
if (candidate == FSTRING_TABLE_EMPTY) {
|
||||
// Not in table
|
||||
if (!inserting) {
|
||||
// Prepare a string suitable for inserting into the table
|
||||
value = build_fstring(value, arg);
|
||||
RUBY_ASSERT(hash_code == fstring_hash(value));
|
||||
inserting = true;
|
||||
}
|
||||
|
||||
unsigned int prev_count = RUBY_ATOMIC_FETCH_ADD(table->count, 1);
|
||||
|
||||
if (UNLIKELY(prev_count > table->capacity / 2)) {
|
||||
fstring_try_resize(table_obj);
|
||||
goto retry;
|
||||
}
|
||||
|
||||
VALUE found = RUBY_ATOMIC_VALUE_CAS(entry->str, FSTRING_TABLE_EMPTY, value);
|
||||
if (found == FSTRING_TABLE_EMPTY) {
|
||||
// Success! Our value was inserted
|
||||
|
||||
// Also set the hash code
|
||||
RUBY_ATOMIC_VALUE_SET(entry->hash, hash_code);
|
||||
|
||||
RB_GC_GUARD(table_obj);
|
||||
return value;
|
||||
}
|
||||
else {
|
||||
// Nothing was inserted
|
||||
RUBY_ATOMIC_DEC(table->count); // we didn't end up inserting
|
||||
|
||||
// Another thread won the race, try again at the same location
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (candidate == FSTRING_TABLE_TOMBSTONE) {
|
||||
// Deleted entry, continue searching
|
||||
}
|
||||
else if (candidate == FSTRING_TABLE_MOVED) {
|
||||
// Wait
|
||||
RB_VM_LOCKING();
|
||||
|
||||
goto retry;
|
||||
}
|
||||
else {
|
||||
VALUE candidate_hash = RUBY_ATOMIC_VALUE_LOAD(entry->hash);
|
||||
if ((candidate_hash == hash_code || candidate_hash == 0) && !fstring_cmp(candidate, value)) {
|
||||
// We've found a match
|
||||
if (UNLIKELY(rb_objspace_garbage_object_p(candidate))) {
|
||||
// This is a weakref table, so after marking but before sweeping is complete we may find a matching garbage object.
|
||||
// Skip it and mark it as a tombstone to help other threads out
|
||||
RUBY_ATOMIC_VALUE_CAS(entry->str, candidate, FSTRING_TABLE_TOMBSTONE);
|
||||
|
||||
// Fall through and continue our search
|
||||
}
|
||||
else {
|
||||
RB_GC_GUARD(table_obj);
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
idx = fstring_table_probe_next(&probe);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Removes an fstring from the table. Compares by identity
|
||||
static void
|
||||
fstring_delete(VALUE hash_code, VALUE value)
|
||||
{
|
||||
// Delete is never called concurrently, so atomic operations are unnecessary
|
||||
VALUE table_obj = RUBY_ATOMIC_VALUE_LOAD(fstring_table_obj);
|
||||
RUBY_ASSERT_ALWAYS(table_obj);
|
||||
struct fstring_table_struct *table = RTYPEDDATA_GET_DATA(table_obj);
|
||||
|
||||
struct fstring_table_probe probe;
|
||||
int idx = fstring_table_probe_start(&probe, table, hash_code);
|
||||
|
||||
for (;;) {
|
||||
struct fstring_table_entry *entry = &table->entries[idx];
|
||||
VALUE candidate = entry->str;
|
||||
|
||||
// Allocations should only occur at the beginning of the resize
|
||||
RUBY_ASSERT(candidate != FSTRING_TABLE_MOVED);
|
||||
|
||||
if (candidate == FSTRING_TABLE_EMPTY) {
|
||||
// We didn't find our string to delete
|
||||
return;
|
||||
}
|
||||
else if (candidate == value) {
|
||||
// We found our string, replace it with a tombstone and increment the count
|
||||
entry->str = FSTRING_TABLE_TOMBSTONE;
|
||||
table->deleted_entries++;
|
||||
return;
|
||||
}
|
||||
|
||||
idx = fstring_table_probe_next(&probe);
|
||||
}
|
||||
}
|
||||
|
||||
static VALUE
|
||||
register_fstring(VALUE str, bool copy, bool force_precompute_hash)
|
||||
{
|
||||
struct fstr_update_arg args = {
|
||||
struct fstr_create_arg args = {
|
||||
.copy = copy,
|
||||
.force_precompute_hash = force_precompute_hash
|
||||
};
|
||||
|
@ -873,8 +577,7 @@ register_fstring(VALUE str, bool copy, bool force_precompute_hash)
|
|||
}
|
||||
#endif
|
||||
|
||||
VALUE hash_code = fstring_hash(str);
|
||||
VALUE result = fstring_find_or_insert(hash_code, str, &args);
|
||||
VALUE result = rb_ractor_safe_set_find_or_insert(&fstring_table_obj, str, &args);
|
||||
|
||||
RUBY_ASSERT(!rb_objspace_garbage_object_p(result));
|
||||
RUBY_ASSERT(RB_TYPE_P(result, T_STRING));
|
||||
|
@ -885,47 +588,6 @@ register_fstring(VALUE str, bool copy, bool force_precompute_hash)
|
|||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
rb_fstring_foreach_with_replace(st_foreach_check_callback_func *func, st_update_callback_func *replace, st_data_t arg)
|
||||
{
|
||||
// Assume locking and barrier (which there is no assert for)
|
||||
ASSERT_vm_locking();
|
||||
|
||||
VALUE table_obj = RUBY_ATOMIC_VALUE_LOAD(fstring_table_obj);
|
||||
if (!table_obj) {
|
||||
// Table not yet initialized. Nothing to iterate over
|
||||
return;
|
||||
}
|
||||
struct fstring_table_struct *table = RTYPEDDATA_GET_DATA(table_obj);
|
||||
|
||||
for (unsigned int i = 0; i < table->capacity; i++) {
|
||||
VALUE key = table->entries[i].str;
|
||||
if(key == FSTRING_TABLE_EMPTY) continue;
|
||||
if(key == FSTRING_TABLE_TOMBSTONE) continue;
|
||||
|
||||
enum st_retval retval;
|
||||
retval = (*func)(key, key, arg, 0);
|
||||
|
||||
if (retval == ST_REPLACE && replace) {
|
||||
st_data_t value = key;
|
||||
retval = (*replace)(&key, &value, arg, TRUE);
|
||||
table->entries[i].str = key;
|
||||
}
|
||||
switch (retval) {
|
||||
case ST_REPLACE:
|
||||
case ST_CONTINUE:
|
||||
break;
|
||||
case ST_CHECK:
|
||||
rb_bug("unsupported");
|
||||
case ST_STOP:
|
||||
return;
|
||||
case ST_DELETE:
|
||||
table->entries[i].str = FSTRING_TABLE_TOMBSTONE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
rb_obj_is_fstring_table(VALUE obj)
|
||||
{
|
||||
|
@ -940,14 +602,21 @@ rb_gc_free_fstring(VALUE obj)
|
|||
// Assume locking and barrier (which there is no assert for)
|
||||
ASSERT_vm_locking();
|
||||
|
||||
VALUE str_hash = fstring_hash(obj);
|
||||
fstring_delete(str_hash, obj);
|
||||
rb_ractor_safe_set_delete_by_identity(fstring_table_obj, obj);
|
||||
|
||||
RB_DEBUG_COUNTER_INC(obj_str_fstr);
|
||||
|
||||
FL_UNSET(obj, RSTRING_FSTR);
|
||||
}
|
||||
|
||||
void
|
||||
rb_fstring_foreach_with_replace(int (*callback)(VALUE *str, void *data), void *data)
|
||||
{
|
||||
if (fstring_table_obj) {
|
||||
rb_ractor_safe_set_foreach_with_replace(fstring_table_obj, callback, data);
|
||||
}
|
||||
}
|
||||
|
||||
static VALUE
|
||||
setup_fake_str(struct RString *fake_str, const char *name, long len, int encidx)
|
||||
{
|
||||
|
@ -1002,22 +671,6 @@ rb_fstring_cstr(const char *ptr)
|
|||
return rb_fstring_new(ptr, strlen(ptr));
|
||||
}
|
||||
|
||||
static int
|
||||
fstring_cmp(VALUE a, VALUE b)
|
||||
{
|
||||
long alen, blen;
|
||||
const char *aptr, *bptr;
|
||||
|
||||
RUBY_ASSERT(RB_TYPE_P(a, T_STRING));
|
||||
RUBY_ASSERT(RB_TYPE_P(b, T_STRING));
|
||||
|
||||
RSTRING_GETMEM(a, aptr, alen);
|
||||
RSTRING_GETMEM(b, bptr, blen);
|
||||
return (alen != blen ||
|
||||
ENCODING_GET(a) != ENCODING_GET(b) ||
|
||||
memcmp(aptr, bptr, alen) != 0);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
single_byte_optimizable(VALUE str)
|
||||
{
|
||||
|
@ -13097,16 +12750,21 @@ rb_yjit_str_concat_codepoint(VALUE str, VALUE codepoint)
|
|||
}
|
||||
#endif
|
||||
|
||||
static int
|
||||
fstring_set_class_i(VALUE *str, void *data)
|
||||
{
|
||||
RBASIC_SET_CLASS(*str, rb_cString);
|
||||
|
||||
return ST_CONTINUE;
|
||||
}
|
||||
|
||||
void
|
||||
Init_String(void)
|
||||
{
|
||||
rb_cString = rb_define_class("String", rb_cObject);
|
||||
struct fstring_table_struct *fstring_table = RTYPEDDATA_GET_DATA(fstring_table_obj);
|
||||
for (unsigned int i = 0; i < fstring_table->capacity; i++) {
|
||||
VALUE str = fstring_table->entries[i].str;
|
||||
if (!str) continue;
|
||||
RBASIC_SET_CLASS(str, rb_cString);
|
||||
}
|
||||
|
||||
rb_ractor_safe_set_foreach_with_replace(fstring_table_obj, fstring_set_class_i, NULL);
|
||||
|
||||
rb_include_module(rb_cString, rb_mComparable);
|
||||
rb_define_alloc_func(rb_cString, empty_str_alloc);
|
||||
rb_define_singleton_method(rb_cString, "new", rb_str_s_new, -1);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue